diff options
110 files changed, 3327 insertions, 1328 deletions
diff --git a/.gitignore b/.gitignore index 8e8dc275db..aa258a6bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -197,6 +197,7 @@ /test-string-list /test-subprocess /test-svn-fe +/test-wildmatch /common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/Makefile b/Documentation/Makefile index e53d333e5c..971977b8aa 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -178,8 +178,6 @@ all: html man html: $(DOC_HTML) -$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf - man: man1 man5 man7 man1: $(DOC_MAN1) man5: $(DOC_MAN5) @@ -257,7 +255,7 @@ clean: $(RM) $(cmds_txt) *.made $(RM) manpage-base-url.xsl -$(MAN_HTML): %.html : %.txt +$(MAN_HTML): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ @@ -270,7 +268,7 @@ manpage-base-url.xsl: manpage-base-url.xsl.in $(QUIET_XMLTO)$(RM) $@ && \ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< -%.xml : %.txt +%.xml : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ @@ -286,7 +284,7 @@ technical/api-index.txt: technical/api-index-skel.txt \ $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ -$(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt +$(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt diff --git a/Documentation/RelNotes/1.8.1.1.txt b/Documentation/RelNotes/1.8.1.1.txt new file mode 100644 index 0000000000..6cde07ba29 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.1.txt @@ -0,0 +1,87 @@ +Git 1.8.1.1 Release Notes +========================= + +Fixes since v1.8.1 +------------------ + + * The attribute mechanism didn't allow limiting attributes to be + applied to only a single directory itself with "path/" like the + exclude mechanism does. + + * When attempting to read the XDG-style $HOME/.config/git/config and + finding that $HOME/.config/git is a file, we gave a wrong error + message, instead of treating the case as "a custom config file does + not exist there" and moving on. + + * After failing to create a temporary file using mkstemp(), failing + pathname was not reported correctly on some platforms. + + * http transport was wrong to ask for the username when the + authentication is done by certificate identity. + + * The behaviour visible to the end users was confusing, when they + attempt to kill a process spawned in the editor that was in turn + launched by Git with SIGINT (or SIGQUIT), as Git would catch that + signal and die. We ignore these signals now. + + * A child process that was killed by a signal (e.g. SIGINT) was + reported in an inconsistent way depending on how the process was + spawned by us, with or without a shell in between. + + * After "git add -N" and then writing a tree object out of the + index, the cache-tree data structure got corrupted. + + * "git apply" misbehaved when fixing whitespace breakages by removing + excess trailing blank lines in some corner cases. + + * A tar archive created by "git archive" recorded a directory in a + way that made NetBSD's implementation of "tar" sometimes unhappy. + + * When "git clone --separate-git-dir=$over_there" is interrupted, it + failed to remove the real location of the $GIT_DIR it created. + This was most visible when interrupting a submodule update. + + * "git fetch --mirror" and fetch that uses other forms of refspec + with wildcard used to attempt to update a symbolic ref that match + the wildcard on the receiving end, which made little sense (the + real ref that is pointed at by the symbolic ref would be updated + anyway). Symbolic refs no longer are affected by such a fetch. + + * The "log --graph" codepath fell into infinite loop in some + corner cases. + + * "git merge" started calling prepare-commit-msg hook like "git + commit" does some time ago, but forgot to pay attention to the exit + status of the hook. + + * "git pack-refs" that ran in parallel to another process that + created new refs had a race that can lose new ones. + + * When a line to be wrapped has a solid run of non space characters + whose length exactly is the wrap width, "git shortlog -w" failed + to add a newline after such a line. + + * The way "git svn" asked for password using SSH_ASKPASS and + GIT_ASKPASS was not in line with the rest of the system. + + * "gitweb", when sorting by age to show repositories with new + activities first, used to sort repositories with absolutely + nothing in it early, which was not very useful. + + * "gitweb", when sorting by age to show repositories with new + activities first, used to sort repositories with absolutely + nothing in it early, which was not very useful. + + * When autoconf is used, any build on a different commit always ran + "config.status --recheck" even when unnecessary. + + * Some scripted programs written in Python did not get updated when + PYTHON_PATH changed. + + * We have been carrying a translated and long-unmaintained copy of an + old version of the tutorial; removed. + + * Portability issues in many self-test scripts have been addressed. + + +Also contains other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.8.1.2.txt b/Documentation/RelNotes/1.8.1.2.txt new file mode 100644 index 0000000000..76ad0b3d01 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.2.txt @@ -0,0 +1,13 @@ +Git 1.8.1.2 Release Notes +========================= + +Fixes since v1.8.1.1 +-------------------- + + * "git archive" did not record uncompressed size in the header when + streaming a zip archive, which confused some implementations of unzip. + + * When users spelled "cc:" in lowercase in the fake "header" in the + trailer part, "git send-email" failed to pick up the addresses from + there. As e-mail headers field names are case insensitive, this + script should follow suit and treat "cc:" and "Cc:" the same way. diff --git a/Documentation/RelNotes/1.8.2.txt b/Documentation/RelNotes/1.8.2.txt index b92a2fe18a..f6d15059dc 100644 --- a/Documentation/RelNotes/1.8.2.txt +++ b/Documentation/RelNotes/1.8.2.txt @@ -38,6 +38,17 @@ UI, Workflows & Features * Scripts can ask Git that wildcard patterns in pathspecs they give do not have any significance, i.e. take them as literal strings. + * The pathspec code learned to grok "foo/**/bar" as a pattern that + matches "bar" in 0-or-more levels of subdirectory in "foo". + + * "git blame" (and "git diff") learned the "--no-follow" option. + + * "git cherry-pick" can be used to replay a root commit to an unborn + branch. + + * "git commit" can be told to use --cleanup=whitespace by setting the + configuration variable commit.cleanup to 'whitespace'. + * "git fetch --mirror" and fetch that uses other forms of refspec with wildcard used to attempt to update a symbolic ref that match the wildcard on the receiving end, which made little sense (the @@ -48,9 +59,25 @@ UI, Workflows & Features is being exported, and uses the description for the branch, when asked to write a cover letter for the series. + * "git format-patch" learned "-v $count" option, and prepends a + string "v$count-" to the names of its output files, and also + automatically sets the subject prefix to "PATCH v$count". This + allows patches from rerolled series to be stored under different + names and makes it easier to reuse cover letter messsages. + + * "git log" and friends can be told with --use-mailmap option to + rewrite the names and email addresses of people using the mailmap + mechanism. + * "git push" now requires "-f" to update a tag, even if it is a fast-forward, as tags are meant to be fixed points. + * When "git rebase" fails to generate patches to be applied (e.g. due + to oom), it failed to detect the failure and instead behaved as if + there were nothing to do. A workaround to use a temporary file has + been applied, but we probably would want to revisit this later, as + it hurts the common case of not failing at all. + * "git submodule" started learning a new mode to integrate with the tip of the remote branch (as opposed to integrating with the commit recorded in the superproject's gitlink). @@ -75,6 +102,10 @@ Performance, Internal Implementation, etc. * The implementation of "imap-send" has been updated to reuse xml quoting code from http-push codepath. + * There is a simple-minded checker for the test scripts in t/ + directory to catch most common mistakes (it is not enabled by + default). + Also contains minor documentation updates and code clean-ups. @@ -103,6 +134,11 @@ details). signal and die. We ignore these signals now. (merge 1250857 pf/editor-ignore-sigint later to maint). + * A child process that was killed by a signal (e.g. SIGINT) was + reported in an inconsistent way depending on how the process was + spawned by us, with or without a shell in between. + (merge 709ca73 jk/unify-exit-code-by-receiving-signal later to maint). + * After failing to create a temporary file using mkstemp(), failing pathname was not reported correctly on some platforms. (merge f7be59b jc/mkstemp-more-careful-error-reporting later to maint). @@ -116,6 +152,19 @@ details). excess trailing blank lines. (merge 5de7166 jc/apply-trailing-blank-removal later to maint). + * A tar archive created by "git archive" recorded a directory in a + way that made NetBSD's implementation of "tar" sometimes unhappy. + (merge 22f0dcd rs/leave-base-name-in-name-field-of-tar later to maint). + + * "git archive" did not record uncompressed size in the header when + streaming a zip archive, which confused some implementations of unzip. + (merge 5ea2c84 rs/zip-with-uncompressed-size-in-the-header later to maint). + + * When "git clone --separate-git-dir=$over_there" is interrupted, it + failed to remove the real location of the $GIT_DIR it created. + This was most visible when interrupting a submodule update. + (merge 9be1980 jl/interrupt-clone-remove-separate-git-dir later to maint). + * The way "git svn" asked for password using SSH_ASKPASS and GIT_ASKPASS was not in line with the rest of the system. (merge e9263e4 ss/svn-prompt later to maint). @@ -136,11 +185,26 @@ details). index, the cache-tree data structure got corrupted. (merge eec3e7e nd/invalidate-i-t-a-cache-tree later to maint). + * "git merge --no-edit" computed who were involved in the work done + on the side branch, even though that information is to be discarded + without getting seen in the editor. + (merge 9bcbb1c jc/maint-fmt-merge-msg-no-edit-lose-credit later to maint). + * "git merge" started calling prepare-commit-msg hook like "git commit" does some time ago, but forgot to pay attention to the exit status of the hook. (merge 3e4141d ap/merge-stop-at-prepare-commit-msg-failure later to maint). + * When users spell "cc:" in lowercase in the fake "header" in the + trailer part, "git send-email" failed to pick up the addresses from + there. As e-mail headers field names are case insensitive, this + script should follow suit and treat "cc:" and "Cc:" the same way. + (merge 6310071 nz/send-email-headers-are-case-insensitive later to maint). + + * Output from "git status --ignored" showed an unexpected interaction + with "--untracked". + (merge a45fb69 ap/status-ignored-in-ignored-directory later to maint). + * "gitweb", when sorting by age to show repositories with new activities first, used to sort repositories with absolutely nothing in it early, which was not very useful. @@ -156,6 +220,14 @@ details). to add a newline after such a line. (merge e0db176 sp/shortlog-missing-lf later to maint). + * Command line completion leaked an unnecessary error message while + looking for possible matches with paths in <tree-ish>. + (merge ca87dd6 ds/completion-silence-in-tree-path-probe later to maint). + + * Command line completion for "tcsh" emitted an unwanted space + after completing a single directory name. + (merge 92f1c04 mk/complete-tcsh later to maint). + * Some shells do not behave correctly when IFS is unset; work it around by explicitly setting it to the default value. (merge 393050c jc/maint-fbsd-sh-ifs-workaround later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index d5809e0e8c..b87f744643 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -923,6 +923,15 @@ column.tag:: Specify whether to output tag listing in `git tag` in columns. See `column.ui` for details. +commit.cleanup:: + This setting overrides the default of the `--cleanup` option in + `git commit`. See linkgit:git-commit[1] for details. Changing the + default can be useful when you always want to keep lines that begin + with comment character `#` in your log message, in which case you + would do `git config commit.cleanup whitespace` (note that you will + have to remove the help lines that begin with `#` in the commit log + template yourself, if you do this). + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit @@ -1361,6 +1370,12 @@ help.autocorrect:: value is 0 - the command will be just shown but not executed. This is the default. +help.htmlpath:: + Specify the path where the HTML documentation resides. File system paths + and URLs are supported. HTML pages will be prefixed with this path when + help is displayed in the 'web' format. This defaults to the documentation + path of your Git installation. + http.proxy:: Override the HTTP proxy, normally configured using the 'http_proxy', 'https_proxy', and 'all_proxy' environment variables (see @@ -1519,6 +1534,10 @@ log.showroot:: Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which normally hide the root commit will now show it. True by default. +log.mailmap:: + If true, makes linkgit:git-log[1], linkgit:git-show[1], and + linkgit:git-whatchanged[1] assume `--use-mailmap`. + mailmap.file:: The location of an augmenting mailmap file. The default mailmap, located in the root of the repository, is loaded diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 6d5a04c83b..a221169515 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -72,13 +72,13 @@ if set: GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE - EMAIL (nb "<", ">" and "\n"s are stripped) In case (some of) these environment variables are not set, the information is taken from the configuration items user.name and user.email, or, if not -present, system user name and the hostname used for outgoing mail (taken +present, the environment variable EMAIL, or, if that is not set, +system user name and the hostname used for outgoing mail (taken from `/etc/mailname` and falling back to the fully qualified hostname when that file does not exist). diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 7bdb039d5e..41b27da325 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -179,7 +179,9 @@ OPTIONS only if the message is to be edited. Otherwise only whitespace removed. The 'verbatim' mode does not change message at all, 'whitespace' removes just leading/trailing whitespace lines - and 'strip' removes both whitespace and commentary. + and 'strip' removes both whitespace and commentary. The default + can be changed by the 'commit.cleanup' configuration variable + (see linkgit:git-config[1]). -e:: --edit:: diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 3da5cc272a..bf1a02a80d 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -33,34 +33,46 @@ the frontend program in use. OPTIONS ------- ---date-format=<fmt>:: - Specify the type of dates the frontend will supply to - fast-import within `author`, `committer` and `tagger` commands. - See ``Date Formats'' below for details about which formats - are supported, and their syntax. --force:: Force updating modified existing branches, even if doing so would cause commits to be lost (as the new commit does not contain the old commit). ---max-pack-size=<n>:: - Maximum size of each output packfile. - The default is unlimited. +--quiet:: + Disable all non-fatal output, making fast-import silent when it + is successful. This option disables the output shown by + \--stats. ---big-file-threshold=<n>:: - Maximum size of a blob that fast-import will attempt to - create a delta for, expressed in bytes. The default is 512m - (512 MiB). Some importers may wish to lower this on systems - with constrained memory. +--stats:: + Display some basic statistics about the objects fast-import has + created, the packfiles they were stored into, and the + memory used by fast-import during this run. Showing this output + is currently the default, but can be disabled with \--quiet. ---depth=<n>:: - Maximum delta depth, for blob and tree deltification. - Default is 10. +Options for Frontends +~~~~~~~~~~~~~~~~~~~~~ ---active-branches=<n>:: - Maximum number of branches to maintain active at once. - See ``Memory Utilization'' below for details. Default is 5. +--cat-blob-fd=<fd>:: + Write responses to `cat-blob` and `ls` queries to the + file descriptor <fd> instead of `stdout`. Allows `progress` + output intended for the end-user to be separated from other + output. + +--date-format=<fmt>:: + Specify the type of dates the frontend will supply to + fast-import within `author`, `committer` and `tagger` commands. + See ``Date Formats'' below for details about which formats + are supported, and their syntax. + +--done:: + Terminate with error if there is no `done` command at the end of + the stream. This option might be useful for detecting errors + that cause the frontend to terminate before it has started to + write a stream. + +Locations of Marks Files +~~~~~~~~~~~~~~~~~~~~~~~~ --export-marks=<file>:: Dumps the internal marks table to <file> when complete. @@ -83,32 +95,33 @@ OPTIONS Like --import-marks but instead of erroring out, silently skips the file if it does not exist. ---relative-marks:: +--[no-]relative-marks:: After specifying --relative-marks the paths specified with --import-marks= and --export-marks= are relative to an internal directory in the current repository. In git-fast-import this means that the paths are relative to the .git/info/fast-import directory. However, other importers may use a different location. ++ +Relative and non-relative marks may be combined by interweaving +--(no-)-relative-marks with the --(import|export)-marks= options. ---no-relative-marks:: - Negates a previous --relative-marks. Allows for combining - relative and non-relative marks by interweaving - --(no-)-relative-marks with the --(import|export)-marks= - options. +Performance and Compression Tuning +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ---cat-blob-fd=<fd>:: - Write responses to `cat-blob` and `ls` queries to the - file descriptor <fd> instead of `stdout`. Allows `progress` - output intended for the end-user to be separated from other - output. +--active-branches=<n>:: + Maximum number of branches to maintain active at once. + See ``Memory Utilization'' below for details. Default is 5. ---done:: - Terminate with error if there is no `done` command at the - end of the stream. - This option might be useful for detecting errors that - cause the frontend to terminate before it has started to - write a stream. +--big-file-threshold=<n>:: + Maximum size of a blob that fast-import will attempt to + create a delta for, expressed in bytes. The default is 512m + (512 MiB). Some importers may wish to lower this on systems + with constrained memory. + +--depth=<n>:: + Maximum delta depth, for blob and tree deltification. + Default is 10. --export-pack-edges=<file>:: After creating a packfile, print a line of data to @@ -119,16 +132,9 @@ OPTIONS as these commits can be used as edge points during calls to 'git pack-objects'. ---quiet:: - Disable all non-fatal output, making fast-import silent when it - is successful. This option disables the output shown by - \--stats. - ---stats:: - Display some basic statistics about the objects fast-import has - created, the packfiles they were stored into, and the - memory used by fast-import during this run. Showing this output - is currently the default, but can be disabled with \--quiet. +--max-pack-size=<n>:: + Maximum size of each output packfile. + The default is unlimited. Performance diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index db55a4e0bb..f2e08d11c1 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -117,7 +117,7 @@ returns an empty string instead. As a special case for the date-type fields, you may specify a format for the date by adding one of `:default`, `:relative`, `:short`, `:local`, -`:iso8601` or `:rfc2822` to the end of the fieldname; e.g. +`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g. `%(taggerdate:relative)`. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 259dce4994..9a914d0159 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -18,7 +18,7 @@ SYNOPSIS [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] [--ignore-if-in-upstream] - [--subject-prefix=Subject-Prefix] + [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] [--cover-letter] [--quiet] [--notes[=<ref>]] [<common diff options>] @@ -166,6 +166,15 @@ will want to ensure that threading is disabled for `git send-email`. allows for useful naming of a patch series, and can be combined with the `--numbered` option. +-v <n>:: +--reroll-count=<n>:: + Mark the series as the <n>-th iteration of the topic. The + output filenames have `v<n>` pretended to them, and the + subject prefix ("PATCH" by default, but configurable via the + `--subject-prefix` option) has ` v<n>` appended to it. E.g. + `--reroll-count=4` may produce `v4-0001-add-makefile.patch` + file that has "Subject: [PATCH v4 1/20] Add makefile" in it. + --to=<email>:: Add a `To:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 08a185db7f..22c0d6e4b1 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -47,6 +47,11 @@ OPTIONS Print out the ref name given on the command line by which each commit was reached. +--use-mailmap:: + Use mailmap file to map author and committer names and email + to canonical real names and email addresses. See + linkgit:git-shortlog[1]. + --full-diff:: Without this flag, "git log -p <path>..." shows commits that touch the specified paths, and diffs about the same specified diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index beff6229c8..f70ef9ded2 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -112,6 +112,11 @@ will be fetched and consulted first during a 'git p4 sync'. Since importing directly from p4 is considerably slower than pulling changes from a git remote, this can be useful in a multi-developer environment. +If there are multiple branches, doing 'git p4 sync' will automatically +use the "BRANCH DETECTION" algorithm to try to partition new changes +into the right branch. This can be overridden with the '--branch' +option to specify just a single branch to update. + Rebase ~~~~~~ @@ -173,9 +178,11 @@ subsequent 'sync' operations. --branch <branch>:: Import changes into given branch. If the branch starts with - 'refs/', it will be used as is, otherwise the path 'refs/heads/' - will be prepended. The default branch is 'master'. If used - with an initial clone, no HEAD will be checked out. + 'refs/', it will be used as is. Otherwise if it does not start + with 'p4/', that prefix is added. The branch is assumed to + name a remote tracking, but this can be modified using + '--import-local', or by giving a full ref name. The default + branch is 'master'. + This example imports a new remote "p4/proj2" into an existing git repository: @@ -287,6 +294,11 @@ These options can be used to modify 'git p4 submit' behavior. to bypass the prompt, causing conflicting commits to be automatically skipped, or to quit trying to apply commits, without prompting. +--branch <branch>:: + After submitting, sync this named branch instead of the default + p4/master. See the "Sync options" section above for more + information. + Rebase options ~~~~~~~~~~~~~~ These options can be used to modify 'git p4 rebase' behavior. @@ -394,8 +406,10 @@ the path elements in the p4 repository. The example above relied on the presence of the p4 branch. Without p4 branches, the same result will occur with: ---- +git init depot +cd depot git config git-p4.branchList main:branch1 -git p4 clone --detect-branches //depot@all +git p4 clone --detect-branches //depot@all . ---- diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index afeb4cdf16..c308e91537 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -56,6 +56,9 @@ OPTIONS line of each entry is indented by `indent1` spaces, and the second and subsequent lines are indented by `indent2` spaces. `width`, `indent1`, and `indent2` default to 76, 6 and 9 respectively. ++ +If width is `0` (zero) then indent the lines of the output without wrapping +them. MAPPING AUTHORS diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 69decb13b0..34d438b0ab 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -346,6 +346,16 @@ Any other arguments are passed directly to 'git log' corresponding git commit hash (this can optionally be followed by a tree-ish to specify which branch should be searched). When given a tree-ish, returns the corresponding SVN revision number. ++ +--before;; + Don't require an exact match if given an SVN revision, instead find + the commit corresponding to the state of the SVN repository (on the + current branch) at the specified revision. ++ +--after;; + Don't require an exact match if given an SVN revision; if there is + not an exact match return the closest match searching forward in the + history. 'set-tree':: You should consider using 'dcommit' instead of this command. diff --git a/Documentation/git.txt b/Documentation/git.txt index c03b7adc97..555250dfa0 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.8.1/git.html[documentation for release 1.8.1] +* link:v1.8.1.1/git.html[documentation for release 1.8.1.1] * release notes for + link:RelNotes/1.8.1.1.txt[1.8.1.1], link:RelNotes/1.8.1.txt[1.8.1]. * link:v1.8.0.3/git.html[documentation for release 1.8.0.3] diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 1b82fe1969..91a6438031 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -108,6 +108,25 @@ PATTERN FORMAT For example, "/{asterisk}.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". +Two consecutive asterisks ("`**`") in patterns matched against +full pathname may have special meaning: + + - A leading "`**`" followed by a slash means match in all + directories. For example, "`**/foo`" matches file or directory + "`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar" + matches file or directory "`bar`" anywhere that is directly + under directory "`foo`". + + - A trailing "/**" matches everything inside. For example, + "abc/**" matches all files inside directory "abc", relative + to the location of the `.gitignore` file, with infinite depth. + + - A slash followed by two consecutive asterisks then a slash + matches zero or more directories. For example, "`a/**/b`" + matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on. + + - Other consecutive asterisks are considered invalid. + NOTES ----- diff --git a/Documentation/technical/api-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt index 43dbe09f73..542946b1ba 100644 --- a/Documentation/technical/api-allocation-growing.txt +++ b/Documentation/technical/api-allocation-growing.txt @@ -5,7 +5,9 @@ Dynamically growing an array using realloc() is error prone and boring. Define your array with: -* a pointer (`ary`) that points at the array, initialized to `NULL`; +* a pointer (`item`) that points at the array, initialized to `NULL` + (although please name the variable based on its contents, not on its + type); * an integer variable (`alloc`) that keeps track of how big the current allocation is, initialized to `0`; @@ -13,22 +15,22 @@ Define your array with: * another integer variable (`nr`) to keep track of how many elements the array currently has, initialized to `0`. -Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n, +Then before adding `n`th element to the item, call `ALLOC_GROW(item, n, alloc)`. This ensures that the array can hold at least `n` elements by calling `realloc(3)` and adjusting `alloc` variable. ------------ -sometype *ary; +sometype *item; size_t nr; size_t alloc for (i = 0; i < nr; i++) - if (we like ary[i] already) + if (we like item[i] already) return; /* we did not like any existing one, so add one */ -ALLOC_GROW(ary, nr + 1, alloc); -ary[nr++] = value you like; +ALLOC_GROW(item, nr + 1, alloc); +item[nr++] = value you like; ------------ You are responsible for updating the `nr` variable. diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index add6f435b5..944fc39fac 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -9,37 +9,40 @@ Data structure -------------- `struct dir_struct` structure is used to pass directory traversal -options to the library and to record the paths discovered. The notable -options are: +options to the library and to record the paths discovered. A single +`struct dir_struct` is used regardless of whether or not the traversal +recursively descends into subdirectories. + +The notable options are: `exclude_per_dir`:: The name of the file to be read in each directory for excluded files (typically `.gitignore`). -`collect_ignored`:: +`flags`:: - Include paths that are to be excluded in the result. + A bit-field of options: -`show_ignored`:: +`DIR_SHOW_IGNORED`::: The traversal is for finding just ignored files, not unignored files. -`show_other_directories`:: +`DIR_SHOW_OTHER_DIRECTORIES`::: Include a directory that is not tracked. -`hide_empty_directories`:: +`DIR_HIDE_EMPTY_DIRECTORIES`::: Do not include a directory that is not tracked and is empty. -`no_gitlinks`:: +`DIR_NO_GITLINKS`::: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory. -The result of the enumeration is left in these fields:: +The result of the enumeration is left in these fields: `entries[]`:: diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index f18b4f4817..5d7d7f2d32 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -55,10 +55,8 @@ The functions above do the following: non-zero. . If the program terminated due to a signal, then the return value is the - signal number - 128, ie. it is negative and so indicates an unusual - condition; a diagnostic is printed. This return value can be passed to - exit(2), which will report the same code to the parent process that a - POSIX shell's $? would report for a program that died from the signal. + signal number + 128, ie. the same value that a POSIX shell's $? would + report. A diagnostic is printed. `start_async`:: @@ -281,6 +281,10 @@ all:: # # Define NO_REGEX if you have no or inferior regex support in your C library. # +# Define CYGWIN_V15_WIN32API if you are using Cygwin v1.7.x but are not +# using the current w32api packages. The recommended approach, however, +# is to update your installation if compilation errors occur. +# # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the # user. # @@ -334,19 +338,6 @@ GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') -uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') -uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') -uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') -uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') - -ifdef MSVC - # avoid the MingW and Cygwin configuration sections - uname_S := Windows - uname_O := Windows -endif - # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall @@ -532,6 +523,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe +TEST_PROGRAMS_NEED_X += test-wildmatch TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@ -704,6 +696,7 @@ LIB_H += userdiff.h LIB_H += utf8.h LIB_H += varint.h LIB_H += walker.h +LIB_H += wildmatch.h LIB_H += wt-status.h LIB_H += xdiff-interface.h LIB_H += xdiff/xdiff.h @@ -773,7 +766,7 @@ LIB_OBJS += log-tree.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o LIB_OBJS += merge.o -LIB_OBJS += merge-file.o +LIB_OBJS += merge-blobs.o LIB_OBJS += merge-recursive.o LIB_OBJS += mergesort.o LIB_OBJS += name-hash.o @@ -838,6 +831,7 @@ LIB_OBJS += utf8.o LIB_OBJS += varint.o LIB_OBJS += version.o LIB_OBJS += walker.o +LIB_OBJS += wildmatch.o LIB_OBJS += wrapper.o LIB_OBJS += write_or_die.o LIB_OBJS += ws.o @@ -945,535 +939,7 @@ EXTLIBS = GIT_USER_AGENT = git/$(GIT_VERSION) -# -# Platform specific tweaks -# - -# We choose to avoid "if .. else if .. else .. endif endif" -# because maintaining the nesting to match is a pain. If -# we had "elif" things would have been much nicer... - -ifeq ($(uname_M),x86_64) - XDL_FAST_HASH = YesPlease -endif -ifeq ($(uname_S),OSF1) - # Need this for u_short definitions et al - BASIC_CFLAGS += -D_OSF_SOURCE - SOCKLEN_T = int - NO_STRTOULL = YesPlease - NO_NSEC = YesPlease -endif -ifeq ($(uname_S),Linux) - NO_STRLCPY = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease - HAVE_DEV_TTY = YesPlease -endif -ifeq ($(uname_S),GNU/kFreeBSD) - NO_STRLCPY = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease -endif -ifeq ($(uname_S),UnixWare) - CC = cc - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - SHELL_PATH = /usr/local/bin/bash - NO_IPV6 = YesPlease - NO_HSTRERROR = YesPlease - NO_MKSTEMPS = YesPlease - BASIC_CFLAGS += -Kthread - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - INSTALL = ginstall - TAR = gtar - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease -endif -ifeq ($(uname_S),SCO_SV) - ifeq ($(uname_R),3.2) - CFLAGS = -O2 - endif - ifeq ($(uname_R),5) - CC = cc - BASIC_CFLAGS += -Kthread - endif - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - SHELL_PATH = /usr/bin/bash - NO_IPV6 = YesPlease - NO_HSTRERROR = YesPlease - NO_MKSTEMPS = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - INSTALL = ginstall - TAR = gtar -endif -ifeq ($(uname_S),Darwin) - NEEDS_CRYPTO_WITH_SSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) - OLD_ICONV = UnfortunatelyYes - endif - ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) - NO_STRLCPY = YesPlease - endif - NO_MEMMEM = YesPlease - USE_ST_TIMESPEC = YesPlease - HAVE_DEV_TTY = YesPlease - COMPAT_OBJS += compat/precompose_utf8.o - BASIC_CFLAGS += -DPRECOMPOSE_UNICODE -endif -ifeq ($(uname_S),SunOS) - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - SHELL_PATH = /bin/bash - SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - NO_MSGFMT_EXTENDED_OPTIONS = YesPlease - HAVE_DEV_TTY = YesPlease - ifeq ($(uname_R),5.6) - SOCKLEN_T = int - NO_HSTRERROR = YesPlease - NO_IPV6 = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.7) - NEEDS_RESOLV = YesPlease - NO_IPV6 = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.8) - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.9) - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - INSTALL = /usr/ucb/install - TAR = gtar - BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H -endif -ifeq ($(uname_O),Cygwin) - ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) - NO_D_TYPE_IN_DIRENT = YesPlease - NO_D_INO_IN_DIRENT = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_IPV6 = YesPlease - OLD_ICONV = UnfortunatelyYes - CYGWIN_V15_WIN32API = YesPlease - endif - NO_THREAD_SAFE_PREAD = YesPlease - NEEDS_LIBICONV = YesPlease - NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes - NO_TRUSTABLE_FILEMODE = UnfortunatelyYes - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - # There are conflicting reports about this. - # On some boxes NO_MMAP is needed, and not so elsewhere. - # Try commenting this out if you suspect MMAP is more efficient - NO_MMAP = YesPlease - X = .exe - COMPAT_OBJS += compat/cygwin.o - UNRELIABLE_FSTAT = UnfortunatelyYes - SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield -endif -ifeq ($(uname_S),FreeBSD) - NEEDS_LIBICONV = YesPlease - OLD_ICONV = YesPlease - NO_MEMMEM = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease - USE_ST_TIMESPEC = YesPlease - ifeq ($(shell expr "$(uname_R)" : '4\.'),2) - PTHREAD_LIBS = -pthread - NO_UINTMAX_T = YesPlease - NO_STRTOUMAX = YesPlease - endif - PYTHON_PATH = /usr/local/bin/python - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),OpenBSD) - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - USE_ST_TIMESPEC = YesPlease - NEEDS_LIBICONV = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),NetBSD) - ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) - NEEDS_LIBICONV = YesPlease - endif - BASIC_CFLAGS += -I/usr/pkg/include - BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib - USE_ST_TIMESPEC = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),AIX) - DEFAULT_PAGER = more - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_STRLCPY = YesPlease - NO_NSEC = YesPlease - FREAD_READS_DIRECTORIES = UnfortunatelyYes - INTERNAL_QSORT = UnfortunatelyYes - NEEDS_LIBICONV = YesPlease - BASIC_CFLAGS += -D_LARGE_FILES - ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) - NO_PTHREADS = YesPlease - else - PTHREAD_LIBS = -lpthread - endif - ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3) - INLINE = '' - endif - GIT_TEST_CMP = cmp -endif -ifeq ($(uname_S),GNU) - # GNU/Hurd - NO_STRLCPY = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease -endif -ifeq ($(uname_S),IRIX) - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_MKDTEMP = YesPlease - # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads - # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), - # git dies with a segmentation fault when trying to access the first - # entry of a reflog. The conservative choice is made to always set - # NO_MMAP. If you suspect that your compiler is not affected by this - # issue, comment out the NO_MMAP statement. - NO_MMAP = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - SHELL_PATH = /usr/gnu/bin/bash - NEEDS_LIBGEN = YesPlease -endif -ifeq ($(uname_S),IRIX64) - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_MKDTEMP = YesPlease - # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads - # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), - # git dies with a segmentation fault when trying to access the first - # entry of a reflog. The conservative choice is made to always set - # NO_MMAP. If you suspect that your compiler is not affected by this - # issue, comment out the NO_MMAP statement. - NO_MMAP = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - SHELL_PATH = /usr/gnu/bin/bash - NEEDS_LIBGEN = YesPlease -endif -ifeq ($(uname_S),HP-UX) - INLINE = __inline - NO_IPV6 = YesPlease - NO_SETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_STRLCPY = YesPlease - NO_MKDTEMP = YesPlease - NO_UNSETENV = YesPlease - NO_HSTRERROR = YesPlease - NO_SYS_SELECT_H = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - NO_NSEC = YesPlease - ifeq ($(uname_R),B.11.00) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - endif - ifeq ($(uname_R),B.10.20) - # Override HP-UX 11.x setting: - INLINE = - SOCKLEN_T = size_t - NO_PREAD = YesPlease - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - endif - GIT_TEST_CMP = cmp -endif -ifeq ($(uname_S),Windows) - GIT_VERSION := $(GIT_VERSION).MSVC - pathsep = ; - NO_PREAD = YesPlease - NEEDS_CRYPTO_WITH_SSL = YesPlease - NO_LIBGEN_H = YesPlease - NO_POLL = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_IPV6 = YesPlease - NO_UNIX_SOCKETS = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOK_R = YesPlease - NO_FNMATCH = YesPlease - NO_MEMMEM = YesPlease - # NEEDS_LIBICONV = YesPlease - NO_ICONV = YesPlease - NO_STRTOUMAX = YesPlease - NO_STRTOULL = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - NO_SVN_TESTS = YesPlease - NO_PERL_MAKEMAKER = YesPlease - RUNTIME_PREFIX = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - USE_WIN32_MMAP = YesPlease - # USE_NED_ALLOCATOR = YesPlease - UNRELIABLE_FSTAT = UnfortunatelyYes - OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo - NO_REGEX = YesPlease - NO_CURL = YesPlease - NO_PYTHON = YesPlease - BLK_SHA1 = YesPlease - NO_POSIX_GOODIES = UnfortunatelyYes - NATIVE_CRLF = YesPlease - DEFAULT_HELP_FORMAT = html - - CC = compat/vcbuild/scripts/clink.pl - AR = compat/vcbuild/scripts/lib.pl - CFLAGS = - BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/winansi.o \ - compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" - BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib - EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib - PTHREAD_LIBS = - lib = -ifndef DEBUG - BASIC_CFLAGS += -GL -Os -MT - BASIC_LDFLAGS += -LTCG - AR += -LTCG -else - BASIC_CFLAGS += -Zi -MTd -endif - X = .exe -endif -ifeq ($(uname_S),Interix) - NO_INITGROUPS = YesPlease - NO_IPV6 = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_STRTOUMAX = YesPlease - NO_NSEC = YesPlease - NO_MKSTEMPS = YesPlease - ifeq ($(uname_R),3.5) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - endif - ifeq ($(uname_R),5.2) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - endif -endif -ifeq ($(uname_S),Minix) - NO_IPV6 = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - NEEDS_LIBGEN = - NEEDS_CRYPTO_WITH_SSL = YesPlease - NEEDS_IDN_WITH_CURL = YesPlease - NEEDS_SSL_WITH_CURL = YesPlease - NEEDS_RESOLV = - NO_HSTRERROR = YesPlease - NO_MMAP = YesPlease - NO_CURL = - NO_EXPAT = -endif -ifeq ($(uname_S),NONSTOP_KERNEL) - # Needs some C99 features, "inline" is just one of them. - # INLINE='' would just replace one set of warnings with another and - # still not compile in c89 mode, due to non-const array initializations. - CC = cc -c99 - # Disable all optimization, seems to result in bad code, with -O or -O2 - # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects - # abends on "git push". Needs more investigation. - CFLAGS = -g -O0 - # We'd want it to be here. - prefix = /usr/local - # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl). - PERL_PATH = ${prefix}/bin/perl - PYTHON_PATH = ${prefix}/bin/python - - # As detected by './configure'. - # Missdetected, hence commented out, see below. - #NO_CURL = YesPlease - # Added manually, see above. - NEEDS_SSL_WITH_CURL = YesPlease - HAVE_LIBCHARSET_H = YesPlease - HAVE_STRINGS_H = YesPlease - NEEDS_LIBICONV = YesPlease - NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease - NO_SYS_SELECT_H = UnfortunatelyYes - NO_D_TYPE_IN_DIRENT = YesPlease - NO_HSTRERROR = YesPlease - NO_STRCASESTR = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - NO_MEMMEM = YesPlease - NO_STRLCPY = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - # Currently libiconv-1.9.1. - OLD_ICONV = UnfortunatelyYes - NO_REGEX = YesPlease - NO_PTHREADS = UnfortunatelyYes - - # Not detected (nor checked for) by './configure'. - # We don't have SA_RESTART on NonStop, unfortunalety. - COMPAT_CFLAGS += -DSA_RESTART=0 - # Apparently needed in compat/fnmatch/fnmatch.c. - COMPAT_CFLAGS += -DHAVE_STRING_H=1 - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - NO_PREAD = YesPlease - NO_MMAP = YesPlease - NO_POLL = YesPlease - NO_INTPTR_T = UnfortunatelyYes - # Bug report 10-120822-4477 submitted to HP NonStop development. - MKDIR_WO_TRAILING_SLASH = YesPlease - # RFE 10-120912-4693 submitted to HP NonStop development. - NO_SETITIMER = UnfortunatelyYes - SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin - SHELL_PATH = /usr/local/bin/bash - # as of H06.25/J06.14, we might better use this - #SHELL_PATH = /usr/coreutils/bin/bash -endif -ifneq (,$(findstring MINGW,$(uname_S))) - pathsep = ; - NO_PREAD = YesPlease - NEEDS_CRYPTO_WITH_SSL = YesPlease - NO_LIBGEN_H = YesPlease - NO_POLL = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_UNIX_SOCKETS = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOK_R = YesPlease - NO_FNMATCH = YesPlease - NO_MEMMEM = YesPlease - NEEDS_LIBICONV = YesPlease - OLD_ICONV = YesPlease - NO_STRTOUMAX = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_SVN_TESTS = YesPlease - NO_PERL_MAKEMAKER = YesPlease - RUNTIME_PREFIX = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - USE_WIN32_MMAP = YesPlease - USE_NED_ALLOCATOR = YesPlease - UNRELIABLE_FSTAT = UnfortunatelyYes - OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo - NO_REGEX = YesPlease - NO_PYTHON = YesPlease - BLK_SHA1 = YesPlease - ETAGS_TARGET = ETAGS - NO_INET_PTON = YesPlease - NO_INET_NTOP = YesPlease - NO_POSIX_GOODIES = UnfortunatelyYes - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 - COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/winansi.o \ - compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o - EXTLIBS += -lws2_32 - PTHREAD_LIBS = - X = .exe - SPARSE_FLAGS = -Wno-one-bit-signed-bitfield -ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) - htmldir = doc/git/html/ - prefix = - INSTALL = /bin/install - EXTLIBS += /mingw/lib/libz.a - NO_R_TO_GCC_LINKER = YesPlease - INTERNAL_QSORT = YesPlease - HAVE_LIBCHARSET_H = YesPlease -else - NO_CURL = YesPlease -endif -endif -ifeq ($(uname_S),QNX) - COMPAT_CFLAGS += -DSA_RESTART=0 - HAVE_STRINGS_H = YesPlease - NEEDS_SOCKET = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - NO_GETPAGESIZE = YesPlease - NO_ICONV = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_NSEC = YesPlease - NO_PTHREADS = YesPlease - NO_R_TO_GCC_LINKER = YesPlease - NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease -endif - +include config.mak.uname -include config.mak.autogen -include config.mak diff --git a/archive-tar.c b/archive-tar.c index 0ba3f25cf5..d1cce46e33 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -153,6 +153,8 @@ static unsigned int ustar_header_chksum(const struct ustar_header *header) static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen) { size_t i = pathlen; + if (i > 1 && path[i - 1] == '/') + i--; if (i > maxlen) i = maxlen; do { diff --git a/archive-zip.c b/archive-zip.c index 55f66b4060..d3aef532b7 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -240,7 +240,7 @@ static int write_zip_entry(struct archiver_args *args, (mode & 0111) ? ((mode) << 16) : 0; if (S_ISREG(mode) && args->compression_level != 0 && size > 0) method = 8; - compressed_size = size; + compressed_size = (method == 0) ? size : 0; if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert && size > big_file_threshold) { @@ -313,10 +313,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); copy_le16(header.mdate, zip_date); - if (flags & ZIP_STREAM) - set_zip_header_data_desc(&header, 0, 0, 0); - else - set_zip_header_data_desc(&header, size, compressed_size, crc); + set_zip_header_data_desc(&header, size, compressed_size, crc); copy_le16(header.filename_length, pathlen); copy_le16(header.extra_length, ZIP_EXTRA_MTIME_SIZE); write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE); @@ -284,7 +284,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, * (reading the file from top to bottom), .gitattribute of the root * directory (again, reading the file from top to bottom) down to the * current directory, and then scan the list backwards to find the first match. - * This is exactly the same as what excluded() does in dir.c to deal with + * This is exactly the same as what is_excluded() does in dir.c to deal with * .gitignore */ @@ -564,25 +564,12 @@ static void bootstrap_attr_stack(void) attr_stack = elem; } -static const char *find_basename(const char *path) -{ - const char *cp, *last_slash = NULL; - - for (cp = path; *cp; cp++) { - if (*cp == '/' && cp[1]) - last_slash = cp; - } - return last_slash ? last_slash + 1 : path; -} - -static void prepare_attr_stack(const char *path) +static void prepare_attr_stack(const char *path, int dirlen) { struct attr_stack *elem, *info; - int dirlen, len; + int len; const char *cp; - dirlen = find_basename(path) - path; - /* * At the bottom of the attribute stack is the built-in * set of attribute definitions, followed by the contents @@ -704,7 +691,7 @@ static int fill_one(const char *what, struct match_attr *a, int rem) if (*n == ATTR__UNKNOWN) { debug_set(what, - a->is_macro ? a->u.attr->name : a->u.pattern, + a->is_macro ? a->u.attr->name : a->u.pat.pattern, attr, v); *n = v; rem--; @@ -762,15 +749,26 @@ static int macroexpand_one(int attr_nr, int rem) static void collect_all_attrs(const char *path) { struct attr_stack *stk; - int i, pathlen, rem; - const char *basename; + int i, pathlen, rem, dirlen; + const char *basename, *cp, *last_slash = NULL; + + for (cp = path; *cp; cp++) { + if (*cp == '/' && cp[1]) + last_slash = cp; + } + pathlen = cp - path; + if (last_slash) { + basename = last_slash + 1; + dirlen = last_slash - path; + } else { + basename = path; + dirlen = 0; + } - prepare_attr_stack(path); + prepare_attr_stack(path, dirlen); for (i = 0; i < attr_nr; i++) check_all_attr[i].value = ATTR__UNKNOWN; - basename = find_basename(path); - pathlen = strlen(path); rem = attr_nr; for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) rem = fill(path, pathlen, basename, stk, rem); @@ -15,7 +15,8 @@ extern const char git_more_info_string[]; extern void prune_packed_objects(int); struct fmt_merge_msg_opts { - unsigned add_title:1; + unsigned add_title:1, + credit_people:1; int shortlog_len; }; diff --git a/builtin/add.c b/builtin/add.c index e664100c71..075312afcd 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -454,7 +454,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (path_excluded(&check, pathspec[i], -1, &dtype)) + if (is_path_excluded(&check, pathspec[i], -1, &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die(_("pathspec '%s' did not match any files"), diff --git a/builtin/blame.c b/builtin/blame.c index cfae569905..b431ba3209 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -42,6 +42,7 @@ static int blank_boundary; static int incremental; static int xdl_opts; static int abbrev = -1; +static int no_whole_file_rename; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -1226,7 +1227,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * The first pass looks for unrenamed path to optimize for * common cases, then we look for renames in the second pass. */ - for (pass = 0; pass < 2; pass++) { + for (pass = 0; pass < 2 - no_whole_file_rename; pass++) { struct origin *(*find)(struct scoreboard *, struct commit *, struct origin *); find = pass ? find_rename : find_origin; @@ -1321,30 +1322,31 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * Information on commits, used for output. */ struct commit_info { - const char *author; - const char *author_mail; + struct strbuf author; + struct strbuf author_mail; unsigned long author_time; - const char *author_tz; + struct strbuf author_tz; /* filled only when asked for details */ - const char *committer; - const char *committer_mail; + struct strbuf committer; + struct strbuf committer_mail; unsigned long committer_time; - const char *committer_tz; + struct strbuf committer_tz; - const char *summary; + struct strbuf summary; }; /* * Parse author/committer line in the commit object buffer */ static void get_ac_line(const char *inbuf, const char *what, - int person_len, char *person, - int mail_len, char *mail, - unsigned long *time, const char **tz) + struct strbuf *name, struct strbuf *mail, + unsigned long *time, struct strbuf *tz) { - int len, tzlen, maillen; - char *tmp, *endp, *timepos, *mailpos; + struct ident_split ident; + size_t len, maillen, namelen; + char *tmp, *endp; + const char *namebuf, *mailbuf; tmp = strstr(inbuf, what); if (!tmp) @@ -1355,69 +1357,61 @@ static void get_ac_line(const char *inbuf, const char *what, len = strlen(tmp); else len = endp - tmp; - if (person_len <= len) { + + if (split_ident_line(&ident, tmp, len)) { error_out: /* Ugh */ - *tz = "(unknown)"; - strcpy(person, *tz); - strcpy(mail, *tz); + tmp = "(unknown)"; + strbuf_addstr(name, tmp); + strbuf_addstr(mail, tmp); + strbuf_addstr(tz, tmp); *time = 0; return; } - memcpy(person, tmp, len); - tmp = person; - tmp += len; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *tz = tmp+1; - tzlen = (person+len)-(tmp+1); + namelen = ident.name_end - ident.name_begin; + namebuf = ident.name_begin; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *time = strtoul(tmp, NULL, 10); - timepos = tmp; + maillen = ident.mail_end - ident.mail_begin; + mailbuf = ident.mail_begin; - *tmp = 0; - while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) - tmp--; - if (tmp <= person) - return; - mailpos = tmp + 1; - *tmp = 0; - maillen = timepos - tmp; - memcpy(mail, mailpos, maillen); + *time = strtoul(ident.date_begin, NULL, 10); - if (!mailmap.nr) - return; - - /* - * mailmap expansion may make the name longer. - * make room by pushing stuff down. - */ - tmp = person + person_len - (tzlen + 1); - memmove(tmp, *tz, tzlen); - tmp[tzlen] = 0; - *tz = tmp; + len = ident.tz_end - ident.tz_begin; + strbuf_add(tz, ident.tz_begin, len); /* * Now, convert both name and e-mail using mailmap */ - if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { - /* Add a trailing '>' to email, since map_user returns plain emails - Note: It already has '<', since we replace from mail+1 */ - mailpos = memchr(mail, '\0', mail_len); - if (mailpos && mailpos-mail < mail_len - 1) { - *mailpos = '>'; - *(mailpos+1) = '\0'; - } - } + map_user(&mailmap, &mailbuf, &maillen, + &namebuf, &namelen); + + strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf); + strbuf_add(name, namebuf, namelen); +} + +static void commit_info_init(struct commit_info *ci) +{ + + strbuf_init(&ci->author, 0); + strbuf_init(&ci->author_mail, 0); + strbuf_init(&ci->author_tz, 0); + strbuf_init(&ci->committer, 0); + strbuf_init(&ci->committer_mail, 0); + strbuf_init(&ci->committer_tz, 0); + strbuf_init(&ci->summary, 0); +} + +static void commit_info_destroy(struct commit_info *ci) +{ + + strbuf_release(&ci->author); + strbuf_release(&ci->author_mail); + strbuf_release(&ci->author_tz); + strbuf_release(&ci->committer); + strbuf_release(&ci->committer_mail); + strbuf_release(&ci->committer_tz); + strbuf_release(&ci->summary); } static void get_commit_info(struct commit *commit, @@ -1427,11 +1421,8 @@ static void get_commit_info(struct commit *commit, int len; const char *subject, *encoding; char *reencoded, *message; - static char author_name[1024]; - static char author_mail[1024]; - static char committer_name[1024]; - static char committer_mail[1024]; - static char summary_buf[1024]; + + commit_info_init(ret); /* * We've operated without save_commit_buffer, so @@ -1449,11 +1440,8 @@ static void get_commit_info(struct commit *commit, encoding = get_log_output_encoding(); reencoded = logmsg_reencode(commit, encoding); message = reencoded ? reencoded : commit->buffer; - ret->author = author_name; - ret->author_mail = author_mail; get_ac_line(message, "\nauthor ", - sizeof(author_name), author_name, - sizeof(author_mail), author_mail, + &ret->author, &ret->author_mail, &ret->author_time, &ret->author_tz); if (!detailed) { @@ -1461,21 +1449,16 @@ static void get_commit_info(struct commit *commit, return; } - ret->committer = committer_name; - ret->committer_mail = committer_mail; get_ac_line(message, "\ncommitter ", - sizeof(committer_name), committer_name, - sizeof(committer_mail), committer_mail, + &ret->committer, &ret->committer_mail, &ret->committer_time, &ret->committer_tz); - ret->summary = summary_buf; len = find_commit_subject(message, &subject); - if (len && len < sizeof(summary_buf)) { - memcpy(summary_buf, subject, len); - summary_buf[len] = 0; - } else { - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - } + if (len) + strbuf_add(&ret->summary, subject, len); + else + strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1)); + free(reencoded); } @@ -1504,15 +1487,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); + printf("author %s\n", ci.author.buf); + printf("author-mail %s\n", ci.author_mail.buf); printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); + printf("author-tz %s\n", ci.author_tz.buf); + printf("committer %s\n", ci.committer.buf); + printf("committer-mail %s\n", ci.committer_mail.buf); printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); + printf("committer-tz %s\n", ci.committer_tz.buf); + printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); if (suspect->previous) { @@ -1520,6 +1503,9 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); write_name_quoted(prev->path, stdout, '\n'); } + + commit_info_destroy(&ci); + return 1; } @@ -1706,11 +1692,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (opt & OUTPUT_ANNOTATE_COMPAT) { const char *name; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; printf("\t(%10s\t%10s\t%d)", name, - format_time(ci.author_time, ci.author_tz, + format_time(ci.author_time, ci.author_tz.buf, show_raw_time), ent->lno + 1 + cnt); } else { @@ -1729,14 +1715,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) const char *name; int pad; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", name, pad, "", format_time(ci.author_time, - ci.author_tz, + ci.author_tz.buf, show_raw_time)); } printf(" %*d) ", @@ -1751,6 +1737,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (sb->final_buf_size && cp[-1] != '\n') putchar('\n'); + + commit_info_destroy(&ci); } static void output(struct scoreboard *sb, int option) @@ -1875,9 +1863,9 @@ static void find_alignment(struct scoreboard *sb, int *option) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) - num = utf8_strwidth(ci.author_mail); + num = utf8_strwidth(ci.author_mail.buf); else - num = utf8_strwidth(ci.author); + num = utf8_strwidth(ci.author.buf); if (longest_author < num) longest_author = num; } @@ -1889,6 +1877,8 @@ static void find_alignment(struct scoreboard *sb, int *option) longest_dst_lines = num; if (largest_score < ent_score(sb, e)) largest_score = ent_score(sb, e); + + commit_info_destroy(&ci); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -2403,6 +2393,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); + DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES); save_commit_buffer = 0; dashdash_pos = 0; @@ -2426,6 +2417,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: + no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); if (0 < abbrev) diff --git a/builtin/clean.c b/builtin/clean.c index 69c1cda906..f4b760bf3d 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,6 +10,7 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" +#include "refs.h" #include "string-list.h" #include "quote.h" @@ -20,6 +21,12 @@ static const char *const builtin_clean_usage[] = { NULL }; +static const char *msg_remove = N_("Removing %s\n"); +static const char *msg_would_remove = N_("Would remove %s\n"); +static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); +static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); +static const char *msg_warn_remove_failed = N_("failed to remove %s"); + static int git_clean_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "clean.requireforce")) @@ -34,11 +41,112 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset) return 0; } +static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, + int dry_run, int quiet, int *dir_gone) +{ + DIR *dir; + struct strbuf quoted = STRBUF_INIT; + struct dirent *e; + int res = 0, ret = 0, gone = 1, original_len = path->len, len, i; + unsigned char submodule_head[20]; + struct string_list dels = STRING_LIST_INIT_DUP; + + *dir_gone = 1; + + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && + !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { + if (!quiet) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), + quoted.buf); + } + + *dir_gone = 0; + return 0; + } + + dir = opendir(path->buf); + if (!dir) { + /* an empty dir could be removed even if it is unreadble */ + res = dry_run ? 0 : rmdir(path->buf); + if (res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + } + return res; + } + + if (path->buf[original_len - 1] != '/') + strbuf_addch(path, '/'); + + len = path->len; + while ((e = readdir(dir)) != NULL) { + struct stat st; + if (is_dot_or_dotdot(e->d_name)) + continue; + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); + if (lstat(path->buf, &st)) + ; /* fall thru */ + else if (S_ISDIR(st.st_mode)) { + if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) + ret = 1; + if (gone) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else + *dir_gone = 0; + continue; + } else { + res = dry_run ? 0 : unlink(path->buf); + if (!res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + continue; + } + + /* path too long, stat fails, or non-directory still exists */ + *dir_gone = 0; + ret = 1; + break; + } + closedir(dir); + + strbuf_setlen(path, original_len); + + if (*dir_gone) { + res = dry_run ? 0 : rmdir(path->buf); + if (!res) + *dir_gone = 1; + else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + } + + if (!*dir_gone && !quiet) { + for (i = 0; i < dels.nr; i++) + printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); + } + string_list_clear(&dels, 0); + return ret; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { - int i; - int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, config_set = 0, errors = 0; + int i, res; + int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; + int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; @@ -49,7 +157,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), - OPT__DRY_RUN(&show_only, N_("dry run")), + OPT__DRY_RUN(&dry_run, N_("dry run")), OPT__FORCE(&force, N_("force")), OPT_BOOLEAN('d', NULL, &remove_directories, N_("remove whole directories")), @@ -77,7 +185,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!show_only && !force) { + if (!dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -n nor -f given; " "refusing to clean")); @@ -149,38 +257,26 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (S_ISDIR(st.st_mode)) { strbuf_addstr(&directory, ent->name); - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || - (matches == MATCHED_EXACTLY))) { - printf(_("Would remove %s\n"), qname); - } else if (remove_directories || - (matches == MATCHED_EXACTLY)) { - if (!quiet) - printf(_("Removing %s\n"), qname); - if (remove_dir_recursively(&directory, - rm_flags) != 0) { - warning(_("failed to remove %s"), qname); + if (remove_directories || (matches == MATCHED_EXACTLY)) { + if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++; + if (gone && !quiet) { + qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } - } else if (show_only) { - printf(_("Would not remove %s\n"), qname); - } else { - printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { if (pathspec && !matches) continue; - qname = quote_path_relative(ent->name, -1, &buf, prefix); - if (show_only) { - printf(_("Would remove %s\n"), qname); - continue; - } else if (!quiet) { - printf(_("Removing %s\n"), qname); - } - if (unlink(ent->name) != 0) { - warning(_("failed to remove %s"), qname); + res = dry_run ? 0 : unlink(ent->name); + if (res) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + warning(_(msg_warn_remove_failed), qname); errors++; + } else if (!quiet) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } } diff --git a/builtin/clone.c b/builtin/clone.c index ec2f75b4f3..36ec99db3f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -704,6 +704,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_origin) die(_("--bare and --origin %s options are incompatible."), option_origin); + if (real_git_dir) + die(_("--bare and --separate-git-dir are incompatible.")); option_no_checkout = 1; } @@ -771,8 +773,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("could not create leading directories of '%s'"), git_dir); set_git_dir_init(git_dir, real_git_dir, 0); - if (real_git_dir) + if (real_git_dir) { git_dir = real_git_dir; + junk_git_dir = real_git_dir; + } if (0 <= option_verbosity) { if (option_bare) diff --git a/builtin/commit.c b/builtin/commit.c index d6dd3df8b1..7c2a3d48b4 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -103,7 +103,7 @@ static enum { CLEANUP_NONE, CLEANUP_ALL } cleanup_mode; -static char *cleanup_arg; +static const char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; @@ -1320,6 +1320,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) include_status = git_config_bool(k, v); return 0; } + if (!strcmp(k, "commit.cleanup")) + return git_config_string(&cleanup_arg, k, v); status = git_gpg_config(k, v, NULL); if (status) diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index e2e27b2c40..d9af43c257 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -232,8 +232,9 @@ static void record_person(int which, struct string_list *people, { char *name_buf, *name, *name_end; struct string_list_item *elem; - const char *field = (which == 'a') ? "\nauthor " : "\ncommitter "; + const char *field; + field = (which == 'a') ? "\nauthor " : "\ncommitter "; name = strstr(commit->buffer, field); if (!name) return; @@ -323,7 +324,8 @@ static void add_people_info(struct strbuf *out, static void shortlog(const char *name, struct origin_data *origin_data, struct commit *head, - struct rev_info *rev, int limit, + struct rev_info *rev, + struct fmt_merge_msg_opts *opts, struct strbuf *out) { int i, count = 0; @@ -335,6 +337,7 @@ static void shortlog(const char *name, int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; const unsigned char *sha1 = origin_data->sha1; + int limit = opts->shortlog_len; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -351,13 +354,15 @@ static void shortlog(const char *name, if (commit->parents && commit->parents->next) { /* do not list a merge but count committer */ - record_person('c', &committers, commit); + if (opts->credit_people) + record_person('c', &committers, commit); continue; } - if (!count) + if (!count && opts->credit_people) /* the 'tip' committer */ record_person('c', &committers, commit); - record_person('a', &authors, commit); + if (opts->credit_people) + record_person('a', &authors, commit); count++; if (subjects.nr > limit) continue; @@ -372,7 +377,8 @@ static void shortlog(const char *name, string_list_append(&subjects, strbuf_detach(&sb, NULL)); } - add_people_info(out, &authors, &committers); + if (opts->credit_people) + add_people_info(out, &authors, &committers); if (count > limit) strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); else @@ -635,7 +641,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, opts->shortlog_len, out); + head, &rev, opts, out); } strbuf_complete_line(out); @@ -690,6 +696,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.add_title = !message; + opts.credit_people = 1; opts.shortlog_len = shortlog_len; ret = fmt_merge_msg(&input, &output, &opts); diff --git a/builtin/log.c b/builtin/log.c index 3899b1d43a..8f0b2e84fe 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -22,6 +22,7 @@ #include "branch.h" #include "streaming.h" #include "version.h" +#include "mailmap.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -30,6 +31,7 @@ static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; static int decoration_given; +static int use_mailmap_config; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -94,16 +96,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; - int quiet = 0, source = 0; + int quiet = 0, source = 0, mailmap = 0; const struct option builtin_log_options[] = { OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), OPT_BOOLEAN(0, "source", &source, N_("show source")), + OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() }; + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | @@ -136,6 +140,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (source) rev->show_source = 1; + if (mailmap) { + rev->mailmap = xcalloc(1, sizeof(struct string_list)); + read_mailmap(rev->mailmap, NULL); + } + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { /* * "log --pretty=raw" is special; ignore UI oriented @@ -351,6 +360,11 @@ static int git_log_config(const char *var, const char *value, void *cb) } if (!prefixcmp(var, "color.decorate.")) return parse_decorate_color_config(var, 15, value); + if (!strcmp(var, "log.mailmap")) { + use_mailmap_config = git_config_bool(var, value); + return 0; + } + if (grep_config(var, value, cb) < 0) return -1; return git_diff_ui_config(var, value, cb); @@ -678,7 +692,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(fmt_patch_suffix) + 1; + int suffix_len = strlen(rev->patch_suffix) + 1; if (output_directory) { strbuf_addstr(&filename, output_directory); @@ -689,7 +703,12 @@ static int reopen_stdout(struct commit *commit, const char *subject, strbuf_addch(&filename, '/'); } - get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename); + if (rev->numbered_files) + strbuf_addf(&filename, "%d", rev->nr); + else if (commit) + fmt_output_commit(&filename, commit, rev); + else + fmt_output_subject(&filename, subject, rev); if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); @@ -773,7 +792,6 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) } static void make_cover_letter(struct rev_info *rev, int use_stdout, - int numbered, int numbered_files, struct commit *origin, int nr, struct commit **list, struct commit *head, const char *branch_name, @@ -796,7 +814,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, committer = git_committer_info(0); if (!use_stdout && - reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet)) + reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, @@ -1060,7 +1078,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int nr = 0, total, i; int use_stdout = 0; int start_number = -1; - int numbered_files = 0; /* _just_ numbers */ + int just_numbers = 0; int ignore_if_in_upstream = 0; int cover_letter = 0; int boundary_count = 0; @@ -1072,6 +1090,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; + int reroll_count = -1; char *branch_name = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, @@ -1085,12 +1104,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("print patches to standard out")), OPT_BOOLEAN(0, "cover-letter", &cover_letter, N_("generate a cover letter")), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, + OPT_BOOLEAN(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), N_("use <sfx> instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, N_("start numbering patches at <n> instead of 1")), + OPT_INTEGER('v', "reroll-count", &reroll_count, + N_("mark the series as Nth re-roll")), { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1164,6 +1185,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (0 < reroll_count) { + struct strbuf sprefix = STRBUF_INIT; + strbuf_addf(&sprefix, "%s v%d", + rev.subject_prefix, reroll_count); + rev.reroll_count = reroll_count; + rev.subject_prefix = strbuf_detach(&sprefix, NULL); + } + if (do_signoff) { const char *committer; const char *endpos; @@ -1354,12 +1383,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) const char *msgid = clean_message_id(in_reply_to); string_list_append(rev.ref_message_ids, msgid); } - rev.numbered_files = numbered_files; + rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, numbered, numbered_files, + make_cover_letter(&rev, use_stdout, origin, nr, list, head, branch_name, quiet); total++; start_number--; @@ -1406,7 +1435,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && - reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet)) + reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 4a9ee690c7..373c573449 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -203,7 +203,7 @@ static void show_ru_info(void) static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce) { int dtype = ce_to_dtype(ce); - return path_excluded(check, ce->name, ce_namelen(ce), &dtype); + return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype); } static void show_files(struct dir_struct *dir) diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 2338832587..be5e514324 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -42,7 +42,7 @@ static int merge_entry(int pos, const char *path) return found; } -static void merge_file(const char *path) +static void merge_one_path(const char *path) { int pos = cache_name_pos(path, strlen(path)); @@ -102,7 +102,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix) } die("git merge-index: unknown option %s", arg); } - merge_file(arg); + merge_one_path(arg); } if (err && !quiet) die("merge program failed"); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 897a563bc6..e0d0b7d28b 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -3,17 +3,15 @@ #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" -#include "merge-file.h" +#include "merge-blobs.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; -static int resolve_directories = 1; struct merge_list { struct merge_list *next; struct merge_list *link; /* other stages for this object */ - unsigned int stage : 2, - flags : 30; + unsigned int stage : 2; unsigned int mode; const char *path; struct blob *blob; @@ -27,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict); static const char *explanation(struct merge_list *entry) { @@ -76,7 +74,7 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_file(path, base, our, their, size); + return merge_blobs(path, base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -174,17 +172,17 @@ static char *traverse_path(const struct traverse_info *info, const struct name_e return make_traverse_path(path, info, n); } -static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) +static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) { struct merge_list *orig, *final; const char *path; - /* If it's already branch1, don't bother showing it */ - if (!branch1) + /* If it's already ours, don't bother showing it */ + if (!ours) return; path = traverse_path(info, result); - orig = create_entry(2, branch1->mode, branch1->sha1, path); + orig = create_entry(2, ours->mode, ours->sha1, path); final = create_entry(0, result->mode, result->sha1, path); final->link = orig; @@ -192,34 +190,35 @@ static void resolve(const struct traverse_info *info, struct name_entry *branch1 add_merge_entry(final); } -static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) +static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3], + int df_conflict) { char *newbase; struct name_entry *p; struct tree_desc t[3]; void *buf0, *buf1, *buf2; - if (!resolve_directories) - return 0; - p = n; - if (!p->mode) { - p++; - if (!p->mode) - p++; + for (p = n; p < n + 3; p++) { + if (p->mode && S_ISDIR(p->mode)) + break; } - if (!S_ISDIR(p->mode)) - return 0; + if (n + 3 <= p) + return; /* there is no tree here */ + newbase = traverse_path(info, p); - buf0 = fill_tree_descriptor(t+0, n[0].sha1); - buf1 = fill_tree_descriptor(t+1, n[1].sha1); - buf2 = fill_tree_descriptor(t+2, n[2].sha1); - merge_trees(t, newbase); + +#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL) + buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); + buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); + buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); +#undef ENTRY_SHA1 + + merge_trees_recursive(t, newbase, df_conflict); free(buf0); free(buf1); free(buf2); free(newbase); - return 1; } @@ -242,18 +241,26 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info static void unresolved(const struct traverse_info *info, struct name_entry n[3]) { struct merge_list *entry = NULL; + int i; + unsigned dirmask = 0, mask = 0; + + for (i = 0; i < 3; i++) { + mask |= (1 << 1); + if (n[i].mode && S_ISDIR(n[i].mode)) + dirmask |= (1 << i); + } - if (unresolved_directory(info, n)) + unresolved_directory(info, n, dirmask && (dirmask != mask)); + + if (dirmask == mask) return; - /* - * Do them in reverse order so that the resulting link - * list has the stages in order - link_entry adds new - * links at the front. - */ - entry = link_entry(3, info, n + 2, entry); - entry = link_entry(2, info, n + 1, entry); - entry = link_entry(1, info, n + 0, entry); + if (n[2].mode && !S_ISDIR(n[2].mode)) + entry = link_entry(3, info, n + 2, entry); + if (n[1].mode && !S_ISDIR(n[1].mode)) + entry = link_entry(2, info, n + 1, entry); + if (n[0].mode && !S_ISDIR(n[0].mode)) + entry = link_entry(1, info, n + 0, entry); add_merge_entry(entry); } @@ -292,20 +299,29 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { + /* Modified identically */ resolve(info, NULL, entry+1); return mask; } + /* "Both added the same" is left unresolved */ } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; } + /* + * If we did not touch a directory but they made it + * into a file, we fall through and unresolved() + * recurses down. Likewise for the opposite case. + */ } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { + /* We modified, they did not touch -- take ours */ resolve(info, NULL, entry+1); return mask; } @@ -315,15 +331,21 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict) { struct traverse_info info; setup_traverse_info(&info, base); + info.data = &df_conflict; info.fn = threeway_callback; traverse_trees(3, t, &info); } +static void merge_trees(struct tree_desc t[3], const char *base) +{ + merge_trees_recursive(t, base, 0); +} + static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { unsigned char sha1[20]; diff --git a/builtin/merge.c b/builtin/merge.c index 3a31c4bc25..9307e9c726 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1222,6 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.add_title = !have_message; opts.shortlog_len = shortlog_len; + opts.credit_people = (0 < option_edit); fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 83605143ac..240bff3efa 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -36,52 +36,28 @@ static void insert_one_record(struct shortlog *log, const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; - char namebuf[1024]; - char emailbuf[1024]; - size_t len; + const char *mailbuf, *namebuf; + size_t namelen, maillen; const char *eol; - const char *boemail, *eoemail; struct strbuf subject = STRBUF_INIT; + struct strbuf namemailbuf = STRBUF_INIT; + struct ident_split ident; - boemail = strchr(author, '<'); - if (!boemail) - return; - eoemail = strchr(boemail, '>'); - if (!eoemail) + if (split_ident_line(&ident, author, strlen(author))) return; - /* copy author name to namebuf, to support matching on both name and email */ - memcpy(namebuf, author, boemail - author); - len = boemail - author; - while (len > 0 && isspace(namebuf[len-1])) - len--; - namebuf[len] = 0; - - /* copy email name to emailbuf, to allow email replacement as well */ - memcpy(emailbuf, boemail+1, eoemail - boemail); - emailbuf[eoemail - boemail - 1] = 0; - - if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { - while (author < boemail && isspace(*author)) - author++; - for (len = 0; - len < sizeof(namebuf) - 1 && author + len < boemail; - len++) - namebuf[len] = author[len]; - while (0 < len && isspace(namebuf[len-1])) - len--; - namebuf[len] = '\0'; - } - else - len = strlen(namebuf); + namebuf = ident.name_begin; + mailbuf = ident.mail_begin; + namelen = ident.name_end - ident.name_begin; + maillen = ident.mail_end - ident.mail_begin; - if (log->email) { - size_t room = sizeof(namebuf) - len - 1; - int maillen = strlen(emailbuf); - snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); - } + map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + strbuf_add(&namemailbuf, namebuf, namelen); + + if (log->email) + strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); - item = string_list_insert(&log->list, namebuf); + item = string_list_insert(&log->list, namemailbuf.buf); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); @@ -89,6 +89,7 @@ struct pretty_print_context { char *notes_message; struct reflog_walk_info *reflog_info; const char *output_encoding; + struct string_list *mailmap; int color; }; diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index b8b7dc2543..5ef0685135 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -55,7 +55,8 @@ program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ -#if defined _LIBC || !defined __GNU_LIBRARY__ +#if defined NO_FNMATCH || defined NO_FNMATCH_CASEFOLD || \ + defined _LIBC || !defined __GNU_LIBRARY__ # if defined STDC_HEADERS || !defined isascii diff --git a/config.mak.uname b/config.mak.uname new file mode 100644 index 0000000000..bea34f0511 --- /dev/null +++ b/config.mak.uname @@ -0,0 +1,539 @@ +# Platform specific Makefile tweaks based on uname detection + +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') +uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') +uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') +uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') + +ifdef MSVC + # avoid the MingW and Cygwin configuration sections + uname_S := Windows + uname_O := Windows +endif + +# We choose to avoid "if .. else if .. else .. endif endif" +# because maintaining the nesting to match is a pain. If +# we had "elif" things would have been much nicer... + +ifeq ($(uname_M),x86_64) + XDL_FAST_HASH = YesPlease +endif +ifeq ($(uname_S),OSF1) + # Need this for u_short definitions et al + BASIC_CFLAGS += -D_OSF_SOURCE + SOCKLEN_T = int + NO_STRTOULL = YesPlease + NO_NSEC = YesPlease +endif +ifeq ($(uname_S),Linux) + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease + HAVE_DEV_TTY = YesPlease +endif +ifeq ($(uname_S),GNU/kFreeBSD) + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease +endif +ifeq ($(uname_S),UnixWare) + CC = cc + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + SHELL_PATH = /usr/local/bin/bash + NO_IPV6 = YesPlease + NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease + BASIC_CFLAGS += -Kthread + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + INSTALL = ginstall + TAR = gtar + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease +endif +ifeq ($(uname_S),SCO_SV) + ifeq ($(uname_R),3.2) + CFLAGS = -O2 + endif + ifeq ($(uname_R),5) + CC = cc + BASIC_CFLAGS += -Kthread + endif + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + SHELL_PATH = /usr/bin/bash + NO_IPV6 = YesPlease + NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + INSTALL = ginstall + TAR = gtar +endif +ifeq ($(uname_S),Darwin) + NEEDS_CRYPTO_WITH_SSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) + OLD_ICONV = UnfortunatelyYes + endif + ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) + NO_STRLCPY = YesPlease + endif + NO_MEMMEM = YesPlease + USE_ST_TIMESPEC = YesPlease + HAVE_DEV_TTY = YesPlease + COMPAT_OBJS += compat/precompose_utf8.o + BASIC_CFLAGS += -DPRECOMPOSE_UNICODE +endif +ifeq ($(uname_S),SunOS) + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + SHELL_PATH = /bin/bash + SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_MSGFMT_EXTENDED_OPTIONS = YesPlease + HAVE_DEV_TTY = YesPlease + ifeq ($(uname_R),5.6) + SOCKLEN_T = int + NO_HSTRERROR = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.7) + NEEDS_RESOLV = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.8) + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.9) + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + INSTALL = /usr/ucb/install + TAR = gtar + BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H +endif +ifeq ($(uname_O),Cygwin) + ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) + NO_D_TYPE_IN_DIRENT = YesPlease + NO_D_INO_IN_DIRENT = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + OLD_ICONV = UnfortunatelyYes + CYGWIN_V15_WIN32API = YesPlease + endif + NO_THREAD_SAFE_PREAD = YesPlease + NEEDS_LIBICONV = YesPlease + NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes + NO_TRUSTABLE_FILEMODE = UnfortunatelyYes + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + # There are conflicting reports about this. + # On some boxes NO_MMAP is needed, and not so elsewhere. + # Try commenting this out if you suspect MMAP is more efficient + NO_MMAP = YesPlease + X = .exe + COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes + SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield +endif +ifeq ($(uname_S),FreeBSD) + NEEDS_LIBICONV = YesPlease + OLD_ICONV = YesPlease + NO_MEMMEM = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + USE_ST_TIMESPEC = YesPlease + ifeq ($(shell expr "$(uname_R)" : '4\.'),2) + PTHREAD_LIBS = -pthread + NO_UINTMAX_T = YesPlease + NO_STRTOUMAX = YesPlease + endif + PYTHON_PATH = /usr/local/bin/python + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),OpenBSD) + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + USE_ST_TIMESPEC = YesPlease + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),NetBSD) + ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) + NEEDS_LIBICONV = YesPlease + endif + BASIC_CFLAGS += -I/usr/pkg/include + BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib + USE_ST_TIMESPEC = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),AIX) + DEFAULT_PAGER = more + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_STRLCPY = YesPlease + NO_NSEC = YesPlease + FREAD_READS_DIRECTORIES = UnfortunatelyYes + INTERNAL_QSORT = UnfortunatelyYes + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -D_LARGE_FILES + ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) + NO_PTHREADS = YesPlease + else + PTHREAD_LIBS = -lpthread + endif + ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3) + INLINE = '' + endif + GIT_TEST_CMP = cmp +endif +ifeq ($(uname_S),GNU) + # GNU/Hurd + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease +endif +ifeq ($(uname_S),IRIX) + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. + NO_MMAP = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + SHELL_PATH = /usr/gnu/bin/bash + NEEDS_LIBGEN = YesPlease +endif +ifeq ($(uname_S),IRIX64) + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. + NO_MMAP = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + SHELL_PATH = /usr/gnu/bin/bash + NEEDS_LIBGEN = YesPlease +endif +ifeq ($(uname_S),HP-UX) + INLINE = __inline + NO_IPV6 = YesPlease + NO_SETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_STRLCPY = YesPlease + NO_MKDTEMP = YesPlease + NO_UNSETENV = YesPlease + NO_HSTRERROR = YesPlease + NO_SYS_SELECT_H = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + NO_NSEC = YesPlease + ifeq ($(uname_R),B.11.00) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + ifeq ($(uname_R),B.10.20) + # Override HP-UX 11.x setting: + INLINE = + SOCKLEN_T = size_t + NO_PREAD = YesPlease + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + GIT_TEST_CMP = cmp +endif +ifeq ($(uname_S),Windows) + GIT_VERSION := $(GIT_VERSION).MSVC + pathsep = ; + NO_PREAD = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + NO_LIBGEN_H = YesPlease + NO_POLL = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + NO_UNIX_SOCKETS = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease + NO_MEMMEM = YesPlease + # NEEDS_LIBICONV = YesPlease + NO_ICONV = YesPlease + NO_STRTOUMAX = YesPlease + NO_STRTOULL = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + NO_SVN_TESTS = YesPlease + NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + # USE_NED_ALLOCATOR = YesPlease + UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo + NO_REGEX = YesPlease + NO_CURL = YesPlease + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + NATIVE_CRLF = YesPlease + DEFAULT_HELP_FORMAT = html + + CC = compat/vcbuild/scripts/clink.pl + AR = compat/vcbuild/scripts/lib.pl + CFLAGS = + BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE + COMPAT_OBJS = compat/msvc.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/dirent.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib + EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib + PTHREAD_LIBS = + lib = +ifndef DEBUG + BASIC_CFLAGS += -GL -Os -MT + BASIC_LDFLAGS += -LTCG + AR += -LTCG +else + BASIC_CFLAGS += -Zi -MTd +endif + X = .exe +endif +ifeq ($(uname_S),Interix) + NO_INITGROUPS = YesPlease + NO_IPV6 = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_STRTOUMAX = YesPlease + NO_NSEC = YesPlease + NO_MKSTEMPS = YesPlease + ifeq ($(uname_R),3.5) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + endif + ifeq ($(uname_R),5.2) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + endif +endif +ifeq ($(uname_S),Minix) + NO_IPV6 = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + NEEDS_LIBGEN = + NEEDS_CRYPTO_WITH_SSL = YesPlease + NEEDS_IDN_WITH_CURL = YesPlease + NEEDS_SSL_WITH_CURL = YesPlease + NEEDS_RESOLV = + NO_HSTRERROR = YesPlease + NO_MMAP = YesPlease + NO_CURL = + NO_EXPAT = +endif +ifeq ($(uname_S),NONSTOP_KERNEL) + # Needs some C99 features, "inline" is just one of them. + # INLINE='' would just replace one set of warnings with another and + # still not compile in c89 mode, due to non-const array initializations. + CC = cc -c99 + # Disable all optimization, seems to result in bad code, with -O or -O2 + # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects + # abends on "git push". Needs more investigation. + CFLAGS = -g -O0 + # We'd want it to be here. + prefix = /usr/local + # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl). + PERL_PATH = ${prefix}/bin/perl + PYTHON_PATH = ${prefix}/bin/python + + # As detected by './configure'. + # Missdetected, hence commented out, see below. + #NO_CURL = YesPlease + # Added manually, see above. + NEEDS_SSL_WITH_CURL = YesPlease + HAVE_LIBCHARSET_H = YesPlease + HAVE_STRINGS_H = YesPlease + NEEDS_LIBICONV = YesPlease + NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease + NO_SYS_SELECT_H = UnfortunatelyYes + NO_D_TYPE_IN_DIRENT = YesPlease + NO_HSTRERROR = YesPlease + NO_STRCASESTR = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_MEMMEM = YesPlease + NO_STRLCPY = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + # Currently libiconv-1.9.1. + OLD_ICONV = UnfortunatelyYes + NO_REGEX = YesPlease + NO_PTHREADS = UnfortunatelyYes + + # Not detected (nor checked for) by './configure'. + # We don't have SA_RESTART on NonStop, unfortunalety. + COMPAT_CFLAGS += -DSA_RESTART=0 + # Apparently needed in compat/fnmatch/fnmatch.c. + COMPAT_CFLAGS += -DHAVE_STRING_H=1 + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + NO_PREAD = YesPlease + NO_MMAP = YesPlease + NO_POLL = YesPlease + NO_INTPTR_T = UnfortunatelyYes + # Bug report 10-120822-4477 submitted to HP NonStop development. + MKDIR_WO_TRAILING_SLASH = YesPlease + # RFE 10-120912-4693 submitted to HP NonStop development. + NO_SETITIMER = UnfortunatelyYes + SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin + SHELL_PATH = /usr/local/bin/bash + # as of H06.25/J06.14, we might better use this + #SHELL_PATH = /usr/coreutils/bin/bash +endif +ifneq (,$(findstring MINGW,$(uname_S))) + pathsep = ; + NO_PREAD = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + NO_LIBGEN_H = YesPlease + NO_POLL = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_UNIX_SOCKETS = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease + NO_MEMMEM = YesPlease + NEEDS_LIBICONV = YesPlease + OLD_ICONV = YesPlease + NO_STRTOUMAX = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_SVN_TESTS = YesPlease + NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + USE_NED_ALLOCATOR = YesPlease + UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo + NO_REGEX = YesPlease + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease + ETAGS_TARGET = ETAGS + NO_INET_PTON = YesPlease + NO_INET_NTOP = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 + COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/dirent.o + EXTLIBS += -lws2_32 + PTHREAD_LIBS = + X = .exe + SPARSE_FLAGS = -Wno-one-bit-signed-bitfield +ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) + htmldir = doc/git/html/ + prefix = + INSTALL = /bin/install + EXTLIBS += /mingw/lib/libz.a + NO_R_TO_GCC_LINKER = YesPlease + INTERNAL_QSORT = YesPlease + HAVE_LIBCHARSET_H = YesPlease +else + NO_CURL = YesPlease +endif +endif +ifeq ($(uname_S),QNX) + COMPAT_CFLAGS += -DSA_RESTART=0 + HAVE_STRINGS_H = YesPlease + NEEDS_SOCKET = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_GETPAGESIZE = YesPlease + NO_ICONV = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_NSEC = YesPlease + NO_PTHREADS = YesPlease + NO_R_TO_GCC_LINKER = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease +endif diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a4c48e179e..14dd5e7ca2 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -397,7 +397,7 @@ __git_complete_revlist_file () *) pfx="$ref:$pfx" ;; esac - __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ + __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \ | sed '/^100... blob /{ s,^.* ,, s,$, , diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh index 8aafb63315..3e3889f2b4 100644 --- a/contrib/completion/git-completion.tcsh +++ b/contrib/completion/git-completion.tcsh @@ -13,6 +13,7 @@ # # To use this completion script: # +# 0) You need tcsh 6.16.00 or newer. # 1) Copy both this file and the bash completion script to ${HOME}. # You _must_ use the name ${HOME}/.git-completion.bash for the # bash script. @@ -24,6 +25,15 @@ # set autolist=ambiguous # It will tell tcsh to list the possible completion choices. +set __git_tcsh_completion_version = `\echo ${tcsh} | \sed 's/\./ /g'` +if ( ${__git_tcsh_completion_version[1]} < 6 || \ + ( ${__git_tcsh_completion_version[1]} == 6 && \ + ${__git_tcsh_completion_version[2]} < 16 ) ) then + echo "git-completion.tcsh: Your version of tcsh is too old, you need version 6.16.00 or newer. Git completion will not work." + exit +endif +unset __git_tcsh_completion_version + set __git_tcsh_completion_original_script = ${HOME}/.git-completion.bash set __git_tcsh_completion_script = ${HOME}/.git-completion.tcsh.bash @@ -64,9 +74,7 @@ fi _\${1} IFS=\$'\n' -if [ \${#COMPREPLY[*]} -gt 0 ]; then - echo "\${COMPREPLY[*]}" | sort | uniq -else +if [ \${#COMPREPLY[*]} -eq 0 ]; then # No completions suggested. In this case, we want tcsh to perform # standard file completion. However, there does not seem to be way # to tell tcsh to do that. To help the user, we try to simulate @@ -85,19 +93,20 @@ else # We don't support ~ expansion: too tricky. if [ "\${TO_COMPLETE:0:1}" != "~" ]; then # Use ls so as to add the '/' at the end of directories. - RESULT=(\`ls -dp \${TO_COMPLETE}* 2> /dev/null\`) - echo \${RESULT[*]} - - # If there is a single completion and it is a directory, - # we output it a second time to trick tcsh into not adding a space - # after it. - if [ \${#RESULT[*]} -eq 1 ] && [ "\${RESULT[0]: -1}" == "/" ]; then - echo \${RESULT[*]} - fi + COMPREPLY=(\`ls -dp \${TO_COMPLETE}* 2> /dev/null\`) fi fi fi +# tcsh does not automatically remove duplicates, so we do it ourselves +echo "\${COMPREPLY[*]}" | sort | uniq + +# If there is a single completion and it is a directory, we output it +# a second time to trick tcsh into not adding a space after it. +if [ \${#COMPREPLY[*]} -eq 1 ] && [ "\${COMPREPLY[0]: -1}" == "/" ]; then + echo "\${COMPREPLY[*]}" +fi + EOF # Don't need this variable anymore, so don't pollute the users environment diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index c7006000a6..328c2dc76d 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -53,7 +53,7 @@ def gittz(tz): return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60) def hgmode(mode): - m = { '0100755': 'x', '0120000': 'l' } + m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') def get_config(config): @@ -720,6 +720,14 @@ def do_export(parser): if peer: parser.repo.push(peer, force=False) +def fix_path(alias, repo, orig_url): + repo_url = util.url(repo.url()) + url = util.url(orig_url) + if str(url) == str(repo_url): + return + cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % repo_url] + subprocess.call(cmd) + def main(args): global prefix, dirname, branches, bmarks global marks, blob_marks, parsed_refs @@ -766,6 +774,9 @@ def main(args): repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias + if not is_tmp: + fix_path(alias, peer or repo, url) + if not os.path.exists(dirname): os.makedirs(dirname) diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh index 3e76d9fb60..7e3967f5b6 100755 --- a/contrib/remote-helpers/test-hg-hg-git.sh +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -109,6 +109,74 @@ setup () { setup +test_expect_success 'executable bit' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + echo alpha > alpha && + chmod 0644 alpha && + git add alpha && + git commit -m "add alpha" && + chmod 0755 alpha && + git add alpha && + git commit -m "set executable bit" && + chmod 0644 alpha && + git add alpha && + git commit -m "clear executable bit" + ) && + + for x in hg git; do + ( + hg_clone_$x gitrepo hgrepo-$x && + cd hgrepo-$x && + hg_log . && + hg manifest -r 1 -v && + hg manifest -v + ) > output-$x && + + git_clone_$x hgrepo-$x gitrepo2-$x && + git_log gitrepo2-$x > log-$x + done && + cp -r log-* output-* /tmp/foo/ && + + test_cmp output-hg output-git && + test_cmp log-hg log-git +' + +test_expect_success 'symlink' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + ln -s alpha beta && + git add beta && + git commit -m "add beta" + ) && + + for x in hg git; do + ( + hg_clone_$x gitrepo hgrepo-$x && + cd hgrepo-$x && + hg_log . && + hg manifest -v + ) > output-$x && + + git_clone_$x hgrepo-$x gitrepo2-$x && + git_log gitrepo2-$x > log-$x + done && + + test_cmp output-hg output-git && + test_cmp log-hg log-git +' + test_expect_success 'merge conflict 1' ' mkdir -p tmp && cd tmp && test_when_finished "cd .. && rm -rf tmp" && diff --git a/contrib/vim/README b/contrib/vim/README index fca1e17251..8f16d06972 100644 --- a/contrib/vim/README +++ b/contrib/vim/README @@ -17,16 +17,6 @@ To install: 1. Copy these files to vim's syntax directory $HOME/.vim/syntax 2. To auto-detect the editing of various git-related filetypes: - $ cat >>$HOME/.vim/filetype.vim <<'EOF' - autocmd BufNewFile,BufRead *.git/COMMIT_EDITMSG setf gitcommit - autocmd BufNewFile,BufRead *.git/config,.gitconfig setf gitconfig - autocmd BufNewFile,BufRead git-rebase-todo setf gitrebase - autocmd BufNewFile,BufRead .msg.[0-9]* - \ if getline(1) =~ '^From.*# This line is ignored.$' | - \ setf gitsendemail | - \ endif - autocmd BufNewFile,BufRead *.git/** - \ if getline(1) =~ '^\x\{40\}\>\|^ref: ' | - \ setf git | - \ endif - EOF + + $ curl http://ftp.vim.org/pub/vim/runtime/filetype.vim | + sed -ne '/^" Git$/, /^$/ p' >>$HOME/.vim/filetype.vim @@ -11,18 +11,21 @@ enum { D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ - P = GIT_PATHSPEC_MAGIC /* other non-alnum, except for ] and } */ + P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ + X = GIT_CNTRL, + U = GIT_PUNCT, + Z = GIT_CNTRL | GIT_SPACE }; -unsigned char sane_ctype[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */ +const unsigned char sane_ctype[256] = { + X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ - A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, P, /* 80.. 95 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ - A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0, /* 112..127 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ /* Nothing in the 128.. range */ }; @@ -3626,6 +3626,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FIND_COPIES_HARDER); else if (!strcmp(arg, "--follow")) DIFF_OPT_SET(options, FOLLOW_RENAMES); + else if (!strcmp(arg, "--no-follow")) + DIFF_OPT_CLR(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) options->use_color = 1; else if (!prefixcmp(arg, "--color=")) { @@ -2,12 +2,15 @@ * This handles recursive filename detection with exclude * files, index knowledge etc.. * + * See Documentation/technical/api-directory-listing.txt + * * Copyright (C) Linus Torvalds, 2005-2006 * Junio Hamano, 2005-2006 */ #include "cache.h" #include "dir.h" #include "refs.h" +#include "wildmatch.h" struct path_simplify { int len; @@ -376,7 +379,7 @@ void parse_exclude_pattern(const char **pattern, } void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which) + int baselen, struct exclude_list *el) { struct exclude *x; int patternlen; @@ -400,8 +403,8 @@ void add_exclude(const char *string, const char *base, x->base = base; x->baselen = baselen; x->flags = flags; - ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); - which->excludes[which->nr++] = x; + ALLOC_GROW(el->excludes, el->nr + 1, el->alloc); + el->excludes[el->nr++] = x; } static void *read_skip_worktree_file_from_index(const char *path, size_t *size) @@ -427,7 +430,11 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size) return data; } -void free_excludes(struct exclude_list *el) +/* + * Frees memory within el which was allocated for exclude patterns and + * the file buffer. Does not free el itself. + */ +void clear_exclude_list(struct exclude_list *el) { int i; @@ -443,7 +450,7 @@ int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, char **buf_p, - struct exclude_list *which, + struct exclude_list *el, int check_index) { struct stat st; @@ -492,7 +499,7 @@ int add_excludes_from_file_to_list(const char *fname, if (buf[i] == '\n') { if (entry != buf + i && entry[0] != '#') { buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, which); + add_exclude(entry, base, baselen, el); } entry = buf + i + 1; } @@ -507,6 +514,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) die("cannot use %s as an exclude file", fname); } +/* + * Loads the per-directory exclude list for the substring of base + * which has a char length of baselen. + */ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) { struct exclude_list *el; @@ -517,7 +528,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX)) return; /* too long a path -- ignore */ - /* Pop the ones that are not the prefix of the path being checked. */ + /* Pop the directories that are not the prefix of the path being checked. */ el = &dir->exclude_list[EXC_DIRS]; while ((stk = dir->exclude_stack) != NULL) { if (stk->baselen <= baselen && @@ -624,25 +635,30 @@ int match_pathname(const char *pathname, int pathlen, namelen -= prefix; } - return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0; + return wildmatch(pattern, name, + ignore_case ? FNM_CASEFOLD : 0) == 0; } -/* Scan the list and let the last match determine the fate. - * Return 1 for exclude, 0 for include and -1 for undecided. +/* + * Scan the given exclude list in reverse to see whether pathname + * should be ignored. The first match (i.e. the last on the list), if + * any, determines the fate. Returns the exclude_list element which + * matched, or NULL for undecided. */ -int excluded_from_list(const char *pathname, - int pathlen, const char *basename, int *dtype, - struct exclude_list *el) +static struct exclude *last_exclude_matching_from_list(const char *pathname, + int pathlen, + const char *basename, + int *dtype, + struct exclude_list *el) { int i; if (!el->nr) - return -1; /* undefined */ + return NULL; /* undefined */ for (i = el->nr - 1; 0 <= i; i--) { struct exclude *x = el->excludes[i]; const char *exclude = x->pattern; - int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1; int prefix = x->nowildcardlen; if (x->flags & EXC_FLAG_MUSTBEDIR) { @@ -657,7 +673,7 @@ int excluded_from_list(const char *pathname, pathlen - (basename - pathname), exclude, prefix, x->patternlen, x->flags)) - return to_exclude; + return x; continue; } @@ -665,28 +681,64 @@ int excluded_from_list(const char *pathname, if (match_pathname(pathname, pathlen, x->base, x->baselen ? x->baselen - 1 : 0, exclude, prefix, x->patternlen, x->flags)) - return to_exclude; + return x; } + return NULL; /* undecided */ +} + +/* + * Scan the list and let the last match determine the fate. + * Return 1 for exclude, 0 for include and -1 for undecided. + */ +int is_excluded_from_list(const char *pathname, + int pathlen, const char *basename, int *dtype, + struct exclude_list *el) +{ + struct exclude *exclude; + exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el); + if (exclude) + return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return -1; /* undecided */ } -static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) +/* + * Loads the exclude lists for the directory containing pathname, then + * scans all exclude lists to determine whether pathname is excluded. + * Returns the exclude_list element which matched, or NULL for + * undecided. + */ +static struct exclude *last_exclude_matching(struct dir_struct *dir, + const char *pathname, + int *dtype_p) { int pathlen = strlen(pathname); int st; + struct exclude *exclude; const char *basename = strrchr(pathname, '/'); basename = (basename) ? basename+1 : pathname; prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_from_list(pathname, pathlen, basename, - dtype_p, &dir->exclude_list[st])) { - case 0: - return 0; - case 1: - return 1; - } + exclude = last_exclude_matching_from_list( + pathname, pathlen, basename, dtype_p, + &dir->exclude_list[st]); + if (exclude) + return exclude; } + return NULL; +} + +/* + * Loads the exclude lists for the directory containing pathname, then + * scans all exclude lists to determine whether pathname is excluded. + * Returns 1 if true, otherwise 0. + */ +static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) +{ + struct exclude *exclude = + last_exclude_matching(dir, pathname, dtype_p); + if (exclude) + return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return 0; } @@ -694,6 +746,7 @@ void path_exclude_check_init(struct path_exclude_check *check, struct dir_struct *dir) { check->dir = dir; + check->exclude = NULL; strbuf_init(&check->path, 256); } @@ -703,32 +756,41 @@ void path_exclude_check_clear(struct path_exclude_check *check) } /* - * Is this name excluded? This is for a caller like show_files() that - * do not honor directory hierarchy and iterate through paths that are - * possibly in an ignored directory. + * For each subdirectory in name, starting with the top-most, checks + * to see if that subdirectory is excluded, and if so, returns the + * corresponding exclude structure. Otherwise, checks whether name + * itself (which is presumably a file) is excluded. * * A path to a directory known to be excluded is left in check->path to * optimize for repeated checks for files in the same excluded directory. */ -int path_excluded(struct path_exclude_check *check, - const char *name, int namelen, int *dtype) +struct exclude *last_exclude_matching_path(struct path_exclude_check *check, + const char *name, int namelen, + int *dtype) { int i; struct strbuf *path = &check->path; + struct exclude *exclude; /* * we allow the caller to pass namelen as an optimization; it * must match the length of the name, as we eventually call - * excluded() on the whole name string. + * is_excluded() on the whole name string. */ if (namelen < 0) namelen = strlen(name); + /* + * If path is non-empty, and name is equal to path or a + * subdirectory of path, name should be excluded, because + * it's inside a directory which is already known to be + * excluded and was previously left in check->path. + */ if (path->len && path->len <= namelen && !memcmp(name, path->buf, path->len) && (!name[path->len] || name[path->len] == '/')) - return 1; + return check->exclude; strbuf_setlen(path, 0); for (i = 0; name[i]; i++) { @@ -736,8 +798,12 @@ int path_excluded(struct path_exclude_check *check, if (ch == '/') { int dt = DT_DIR; - if (excluded(check->dir, path->buf, &dt)) - return 1; + exclude = last_exclude_matching(check->dir, + path->buf, &dt); + if (exclude) { + check->exclude = exclude; + return exclude; + } } strbuf_addch(path, ch); } @@ -745,7 +811,22 @@ int path_excluded(struct path_exclude_check *check, /* An entry in the index; cannot be a directory with subentries */ strbuf_setlen(path, 0); - return excluded(check->dir, name, dtype); + return last_exclude_matching(check->dir, name, dtype); +} + +/* + * Is this name excluded? This is for a caller like show_files() that + * do not honor directory hierarchy and iterate through paths that are + * possibly in an ignored directory. + */ +int is_path_excluded(struct path_exclude_check *check, + const char *name, int namelen, int *dtype) +{ + struct exclude *exclude = + last_exclude_matching_path(check, name, namelen, dtype); + if (exclude) + return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; + return 0; } static struct dir_entry *dir_entry_new(const char *pathname, int len) @@ -761,7 +842,8 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) { - if (cache_name_exists(pathname, len, ignore_case)) + if (!(dir->flags & DIR_SHOW_IGNORED) && + cache_name_exists(pathname, len, ignore_case)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); @@ -863,8 +945,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * traversal routine. * * Case 1: If we *already* have entries in the index under that - * directory name, we always recurse into the directory to see - * all the files. + * directory name, we recurse into the directory to see all the files, + * unless the directory is excluded and we want to show ignored + * directories * * Case 2: If we *already* have that directory name as a gitlink, * we always continue to see it as a gitlink, regardless of whether @@ -878,6 +961,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * just a directory, unless "hide_empty_directories" is * also true and the directory is empty, in which case * we just ignore it entirely. + * if we are looking for ignored directories, look if it + * contains only ignored files to decide if it must be shown as + * ignored or not. * (b) if it looks like a git directory, and we don't have * 'no_gitlinks' set we treat it as a gitlink, and show it * as a directory. @@ -890,12 +976,15 @@ enum directory_treatment { }; static enum directory_treatment treat_directory(struct dir_struct *dir, - const char *dirname, int len, + const char *dirname, int len, int exclude, const struct path_simplify *simplify) { /* The "len-1" is to strip the final '/' */ switch (directory_exists_in_index(dirname, len-1)) { case index_directory: + if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude) + break; + return recurse_into_directory; case index_gitdir: @@ -915,7 +1004,23 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, } /* This is the "show_other_directories" case */ - if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) + + /* + * We are looking for ignored files and our directory is not ignored, + * check if it contains only ignored files + */ + if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) { + int ignored; + dir->flags &= ~DIR_SHOW_IGNORED; + dir->flags |= DIR_HIDE_EMPTY_DIRECTORIES; + ignored = read_directory_recursive(dir, dirname, len, 1, simplify); + dir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES; + dir->flags |= DIR_SHOW_IGNORED; + + return ignored ? ignore_directory : show_directory; + } + if (!(dir->flags & DIR_SHOW_IGNORED) && + !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) return show_directory; if (!read_directory_recursive(dir, dirname, len, 1, simplify)) return ignore_directory; @@ -923,6 +1028,45 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, } /* + * Decide what to do when we find a file while traversing the + * filesystem. Mostly two cases: + * + * 1. We are looking for ignored files + * (a) File is ignored, include it + * (b) File is in ignored path, include it + * (c) File is not ignored, exclude it + * + * 2. Other scenarios, include the file if not excluded + * + * Return 1 for exclude, 0 for include. + */ +static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype) +{ + struct path_exclude_check check; + int exclude_file = 0; + + if (exclude) + exclude_file = !(dir->flags & DIR_SHOW_IGNORED); + else if (dir->flags & DIR_SHOW_IGNORED) { + /* Always exclude indexed files */ + struct cache_entry *ce = index_name_exists(&the_index, + path->buf, path->len, ignore_case); + + if (ce) + return 1; + + path_exclude_check_init(&check, dir); + + if (!is_path_excluded(&check, path->buf, path->len, dtype)) + exclude_file = 1; + + path_exclude_check_clear(&check); + } + + return exclude_file; +} + +/* * This is an inexact early pruning of any recursive directory * reading - if the path cannot possibly be in the pathspec, * return true, and we'll skip it early. @@ -1045,7 +1189,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, const struct path_simplify *simplify, int dtype, struct dirent *de) { - int exclude = excluded(dir, path->buf, &dtype); + int exclude = is_excluded(dir, path->buf, &dtype); if (exclude && (dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path->buf, path->len, simplify)) dir_add_ignored(dir, path->buf, path->len); @@ -1060,27 +1204,14 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, if (dtype == DT_UNKNOWN) dtype = get_dtype(de, path->buf, path->len); - /* - * Do we want to see just the ignored files? - * We still need to recurse into directories, - * even if we don't ignore them, since the - * directory may contain files that we do.. - */ - if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { - if (dtype != DT_DIR) - return path_ignored; - } - switch (dtype) { default: return path_ignored; case DT_DIR: strbuf_addch(path, '/'); - switch (treat_directory(dir, path->buf, path->len, simplify)) { + + switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) { case show_directory: - if (exclude != !!(dir->flags - & DIR_SHOW_IGNORED)) - return path_ignored; break; case recurse_into_directory: return path_recurse; @@ -1090,7 +1221,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, break; case DT_REG: case DT_LNK: - break; + switch (treat_file(dir, path, exclude, &dtype)) { + case 1: + return path_ignored; + default: + break; + } } return path_handled; } @@ -1,6 +1,8 @@ #ifndef DIR_H #define DIR_H +/* See Documentation/technical/api-directory-listing.txt */ + #include "strbuf.h" struct dir_entry { @@ -13,6 +15,12 @@ struct dir_entry { #define EXC_FLAG_MUSTBEDIR 8 #define EXC_FLAG_NEGATIVE 16 +/* + * Each .gitignore file will be parsed into patterns which are then + * appended to the relevant exclude_list (either EXC_DIRS or + * EXC_FILE). exclude_lists are also used to represent the list of + * --exclude values passed via CLI args (EXC_CMDL). + */ struct exclude_list { int nr; int alloc; @@ -26,9 +34,15 @@ struct exclude_list { } **excludes; }; +/* + * The contents of the per-directory exclude files are lazily read on + * demand and then cached in memory, one per exclude_stack struct, in + * order to avoid opening and parsing each one every time that + * directory is traversed. + */ struct exclude_stack { - struct exclude_stack *prev; - char *filebuf; + struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */ + char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */ int baselen; int exclude_ix; }; @@ -59,6 +73,14 @@ struct dir_struct { #define EXC_DIRS 1 #define EXC_FILE 2 + /* + * Temporary variables which are used during loading of the + * per-directory exclude lists. + * + * exclude_stack points to the top of the exclude_stack, and + * basebuf contains the full path to the current + * (sub)directory in the traversal. + */ struct exclude_stack *exclude_stack; char basebuf[PATH_MAX]; }; @@ -76,8 +98,8 @@ extern int within_depth(const char *name, int namelen, int depth, int max_depth) extern int fill_directory(struct dir_struct *dir, const char **pathspec); extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); -extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, - int *dtype, struct exclude_list *el); +extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename, + int *dtype, struct exclude_list *el); struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len); /* @@ -91,26 +113,29 @@ extern int match_pathname(const char *, int, const char *, int, int, int); /* - * The excluded() API is meant for callers that check each level of leading - * directory hierarchies with excluded() to avoid recursing into excluded + * The is_excluded() API is meant for callers that check each level of leading + * directory hierarchies with is_excluded() to avoid recursing into excluded * directories. Callers that do not do so should use this API instead. */ struct path_exclude_check { struct dir_struct *dir; + struct exclude *exclude; struct strbuf path; }; extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *); extern void path_exclude_check_clear(struct path_exclude_check *); -extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype); +extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *, + int namelen, int *dtype); +extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype); extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, - char **buf_p, struct exclude_list *which, int check_index); + char **buf_p, struct exclude_list *el, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen); extern void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which); -extern void free_excludes(struct exclude_list *el); + int baselen, struct exclude_list *el); +extern void clear_exclude_list(struct exclude_list *el); extern int file_exists(const char *); extern int is_inside_dir(const char *dir); @@ -51,7 +51,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en sigchain_push(SIGINT, SIG_IGN); sigchain_push(SIGQUIT, SIG_IGN); ret = finish_command(&p); - sig = ret + 128; + sig = ret - 128; sigchain_pop(SIGINT); sigchain_pop(SIGQUIT); if (sig == SIGINT || sig == SIGQUIT) diff --git a/git-compat-util.h b/git-compat-util.h index 2cecf56eb3..e5a4b7450b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -528,13 +528,19 @@ extern const char tolower_trans_tbl[256]; #undef isupper #undef tolower #undef toupper -extern unsigned char sane_ctype[256]; +#undef iscntrl +#undef ispunct +#undef isxdigit + +extern const unsigned char sane_ctype[256]; #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 #define GIT_ALPHA 0x04 #define GIT_GLOB_SPECIAL 0x08 #define GIT_REGEX_SPECIAL 0x10 #define GIT_PATHSPEC_MAGIC 0x20 +#define GIT_CNTRL 0x40 +#define GIT_PUNCT 0x80 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) #define isascii(x) (((x) & ~0x7f) == 0) #define isspace(x) sane_istest(x,GIT_SPACE) @@ -546,6 +552,10 @@ extern unsigned char sane_ctype[256]; #define isupper(x) sane_iscase(x, 0) #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) #define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL) +#define iscntrl(x) (sane_istest(x,GIT_CNTRL)) +#define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \ + GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC) +#define isxdigit(x) (hexval_table[x] != -1) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) #define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC) @@ -553,29 +553,49 @@ def gitConfigList(key): _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep) return _gitConfig[key] -def p4BranchesInGit(branchesAreInRemotes = True): +def p4BranchesInGit(branchesAreInRemotes=True): + """Find all the branches whose names start with "p4/", looking + in remotes or heads as specified by the argument. Return + a dictionary of { branch: revision } for each one found. + The branch names are the short names, without any + "p4/" prefix.""" + branches = {} cmdline = "git rev-parse --symbolic " if branchesAreInRemotes: - cmdline += " --remotes" + cmdline += "--remotes" else: - cmdline += " --branches" + cmdline += "--branches" for line in read_pipe_lines(cmdline): line = line.strip() - ## only import to p4/ - if not line.startswith('p4/') or line == "p4/HEAD": + # only import to p4/ + if not line.startswith('p4/'): + continue + # special symbolic ref to p4/master + if line == "p4/HEAD": continue - branch = line - # strip off p4 - branch = re.sub ("^p4/", "", line) + # strip off p4/ prefix + branch = line[len("p4/"):] branches[branch] = parseRevision(line) + return branches +def branch_exists(branch): + """Make sure that the given ref name really exists.""" + + cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, _ = p.communicate() + if p.returncode: + return False + # expect exactly one line of output: the branch name + return out.rstrip() == branch + def findUpstreamBranchPoint(head = "HEAD"): branches = p4BranchesInGit() # map from depot-path to branch name @@ -907,7 +927,8 @@ class P4Submit(Command, P4UserMap): optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"), optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"), optparse.make_option("--conflict", dest="conflict_behavior", - choices=self.conflict_behavior_choices) + choices=self.conflict_behavior_choices), + optparse.make_option("--branch", dest="branch"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -920,6 +941,7 @@ class P4Submit(Command, P4UserMap): self.isWindows = (platform.system() == "Windows") self.exportLabels = False self.p4HasMoveCommand = p4_has_move_command() + self.branch = None def check(self): if len(p4CmdList("opened ...")) > 0: @@ -1656,6 +1678,8 @@ class P4Submit(Command, P4UserMap): print "All commits applied!" sync = P4Sync() + if self.branch: + sync.branch = self.branch sync.run([]) rebase = P4Rebase() @@ -2509,13 +2533,6 @@ class P4Sync(Command, P4UserMap): branch = branch[len(self.projectName):] self.knownBranches[branch] = branch - def listExistingP4GitBranches(self): - # branches holds mapping from name to commit - branches = p4BranchesInGit(self.importIntoRemotes) - self.p4BranchesInGit = branches.keys() - for branch in branches.keys(): - self.initialParents[self.refPrefix + branch] = branches[branch] - def updateOptionDict(self, d): option_keys = {} if self.keepRepoPath: @@ -2687,6 +2704,7 @@ class P4Sync(Command, P4UserMap): files = self.extractFilesFromCommit(description) self.commit(description, files, self.branch, self.initialParent) + # only needed once, to connect to the previous commit self.initialParent = "" except IOError: print self.gitError.read() @@ -2752,34 +2770,31 @@ class P4Sync(Command, P4UserMap): def run(self, args): self.depotPaths = [] self.changeRange = "" - self.initialParent = "" self.previousDepotPaths = [] + self.hasOrigin = False # map from branch depot path to parent branch self.knownBranches = {} self.initialParents = {} - self.hasOrigin = originP4BranchesExist() - if not self.syncWithOrigin: - self.hasOrigin = False if self.importIntoRemotes: self.refPrefix = "refs/remotes/p4/" else: self.refPrefix = "refs/heads/p4/" - if self.syncWithOrigin and self.hasOrigin: - if not self.silent: - print "Syncing with origin first by calling git fetch origin" - system("git fetch origin") + if self.syncWithOrigin: + self.hasOrigin = originP4BranchesExist() + if self.hasOrigin: + if not self.silent: + print 'Syncing with origin first, using "git fetch origin"' + system("git fetch origin") + branch_arg_given = bool(self.branch) if len(self.branch) == 0: self.branch = self.refPrefix + "master" if gitBranchExists("refs/heads/p4") and self.importIntoRemotes: system("git update-ref %s refs/heads/p4" % self.branch) - system("git branch -D p4"); - # create it /after/ importing, when master exists - if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch): - system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch)) + system("git branch -D p4") # accept either the command-line option, or the configuration variable if self.useClientSpec: @@ -2796,12 +2811,25 @@ class P4Sync(Command, P4UserMap): if args == []: if self.hasOrigin: createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent) - self.listExistingP4GitBranches() + + # branches holds mapping from branch name to sha1 + branches = p4BranchesInGit(self.importIntoRemotes) + + # restrict to just this one, disabling detect-branches + if branch_arg_given: + short = self.branch.split("/")[-1] + if short in branches: + self.p4BranchesInGit = [ short ] + else: + self.p4BranchesInGit = branches.keys() if len(self.p4BranchesInGit) > 1: if not self.silent: print "Importing from/into multiple branches" self.detectBranches = True + for branch in branches.keys(): + self.initialParents[self.refPrefix + branch] = \ + branches[branch] if self.verbose: print "branches: %s" % self.p4BranchesInGit @@ -2838,13 +2866,21 @@ class P4Sync(Command, P4UserMap): if p4Change > 0: self.depotPaths = sorted(self.previousDepotPaths) self.changeRange = "@%s,#head" % p4Change - if not self.detectBranches: - self.initialParent = parseRevision(self.branch) if not self.silent and not self.detectBranches: print "Performing incremental import into %s git branch" % self.branch + # accept multiple ref name abbreviations: + # refs/foo/bar/branch -> use it exactly + # p4/branch -> prepend refs/remotes/ or refs/heads/ + # branch -> prepend refs/remotes/p4/ or refs/heads/p4/ if not self.branch.startswith("refs/"): - self.branch = "refs/heads/" + self.branch + if self.importIntoRemotes: + prepend = "refs/remotes/" + else: + prepend = "refs/heads/" + if not self.branch.startswith("p4/"): + prepend += "p4/" + self.branch = prepend + self.branch if len(args) == 0 and self.depotPaths: if not self.silent: @@ -2955,8 +2991,21 @@ class P4Sync(Command, P4UserMap): else: # catch "git p4 sync" with no new branches, in a repo that # does not have any existing p4 branches - if len(args) == 0 and not self.p4BranchesInGit: - die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here."); + if len(args) == 0: + if not self.p4BranchesInGit: + die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.") + + # The default branch is master, unless --branch is used to + # specify something else. Make sure it exists, or complain + # nicely about how to use --branch. + if not self.detectBranches: + if not branch_exists(self.branch): + if branch_arg_given: + die("Error: branch %s does not exist." % self.branch) + else: + die("Error: no branch %s; perhaps specify one with --branch." % + self.branch) + if self.verbose: print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), self.changeRange) @@ -2974,6 +3023,14 @@ class P4Sync(Command, P4UserMap): self.updatedBranches = set() + if not self.detectBranches: + if args: + # start a new branch + self.initialParent = "" + else: + # build on a previous revision + self.initialParent = parseRevision(self.branch) + self.importChanges(changes) if not self.silent: @@ -3006,6 +3063,13 @@ class P4Sync(Command, P4UserMap): read_pipe("git update-ref -d %s" % branch) os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation)) + # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow + # a convenient shortcut refname "p4". + if self.importIntoRemotes: + head_ref = self.refPrefix + "HEAD" + if not gitBranchExists(head_ref) and gitBranchExists(self.branch): + system(["git", "symbolic-ref", head_ref, self.branch]) + return True class P4Rebase(Command): @@ -3113,17 +3177,15 @@ class P4Clone(P4Sync): if not P4Sync.run(self, depotPaths): return False - if self.branch != "master": - if self.importIntoRemotes: - masterbranch = "refs/remotes/p4/master" - else: - masterbranch = "refs/heads/p4/master" - if gitBranchExists(masterbranch): - system("git branch master %s" % masterbranch) - if not self.cloneBare: - system("git checkout -f") - else: - print "Could not detect main branch. No checkout/master branch created." + + # create a master branch and check out a work tree + if gitBranchExists(self.branch): + system([ "git", "branch", "master", self.branch ]) + if not self.cloneBare: + system([ "git", "checkout", "-f" ]) + else: + print 'Not checking out any branch, use ' \ + '"git checkout -q -b master <branch>"' # auto-set this variable if invoked with --use-client-spec if self.useClientSpec_from_options: diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 392ebc9790..97f31dc7af 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -18,6 +18,7 @@ esac test -n "$rebase_root" && root_flag=--root +ret=0 if test -n "$keep_empty" then # we have to do this the hard way. git format-patch completely squashes @@ -25,13 +26,49 @@ then # itself well to recording empty patches. fortunately, cherry-pick # makes this easy git cherry-pick --allow-empty "$revisions" + ret=$? else + rm -f "$GIT_DIR/rebased-patches" + git format-patch -k --stdout --full-index --ignore-if-in-upstream \ --src-prefix=a/ --dst-prefix=b/ \ - --no-renames $root_flag "$revisions" | - git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" -fi && move_to_original_branch + --no-renames $root_flag "$revisions" >"$GIT_DIR/rebased-patches" + ret=$? + + if test 0 != $ret + then + rm -f "$GIT_DIR/rebased-patches" + case "$head_name" in + refs/heads/*) + git checkout -q "$head_name" + ;; + *) + git checkout -q "$orig_head" + ;; + esac + + cat >&2 <<-EOF + + git encountered an error while preparing the patches to replay + these revisions: + + $revisions + + As a result, git cannot rebase them. + EOF + exit $? + fi + + git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" <"$GIT_DIR/rebased-patches" + ret=$? + + rm -f "$GIT_DIR/rebased-patches" +fi + +if test 0 != $ret +then + test -d "$state_dir" && write_basic_state + exit $ret +fi -ret=$? -test 0 != $ret -a -d "$state_dir" && write_basic_state -exit $ret +move_to_original_branch diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 44901d53c4..8ed7fccc18 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -190,6 +190,11 @@ is_empty_commit() { test "$tree" = "$ptree" } +is_merge_commit() +{ + git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1 +} + # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and # GIT_AUTHOR_DATE exported from the current environment. do_with_author () { @@ -874,7 +879,7 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \ while read -r shortsha1 rest do - if test -z "$keep_empty" && is_empty_commit $shortsha1 + if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1 then comment_out="# " else diff --git a/git-remote-testpy.py b/git-remote-testpy.py index e4533b187d..d94a66a870 100644 --- a/git-remote-testpy.py +++ b/git-remote-testpy.py @@ -164,6 +164,11 @@ def do_import(repo, args): ref = line[7:].strip() refs.append(ref) + print "feature done" + + if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): + die('Told to fail') + repo = update_local_repo(repo) repo.exporter.export_repo(repo.gitdir, refs) @@ -177,6 +182,9 @@ def do_export(repo, args): if not repo.gitdir: die("Need gitdir to export") + if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): + die('Told to fail') + update_local_repo(repo) changed = repo.importer.do_import(repo.gitdir) diff --git a/git-send-email.perl b/git-send-email.perl index 94c7f76a15..be809e5b59 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1285,10 +1285,10 @@ foreach my $t (@files) { } if (defined $input_format && $input_format eq 'mbox') { - if (/^Subject:\s+(.*)$/) { + if (/^Subject:\s+(.*)$/i) { $subject = $1; } - elsif (/^From:\s+(.*)$/) { + elsif (/^From:\s+(.*)$/i) { ($author, $author_encoding) = unquote_rfc2047($1); next if $suppress_cc{'author'}; next if $suppress_cc{'self'} and $author eq $sender; @@ -1296,14 +1296,14 @@ foreach my $t (@files) { $1, $_) unless $quiet; push @cc, $1; } - elsif (/^To:\s+(.*)$/) { + elsif (/^To:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { printf("(mbox) Adding to: %s from line '%s'\n", $addr, $_) unless $quiet; push @to, $addr; } } - elsif (/^Cc:\s+(.*)$/) { + elsif (/^Cc:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { if (unquote_rfc2047($addr) eq $sender) { next if ($suppress_cc{'self'}); @@ -1325,7 +1325,7 @@ foreach my $t (@files) { elsif (/^Message-Id: (.*)/i) { $message_id = $1; } - elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { + elsif (!/^Date:\s/i && /^[-A-Za-z]+:\s+\S/) { push @xh, $_; } diff --git a/git-svn.perl b/git-svn.perl index bd5266c86b..d0866946ce 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -114,6 +114,7 @@ my ($_stdin, $_help, $_edit, $_message, $_file, $_branch_dest, $_template, $_shared, $_version, $_fetch_all, $_no_rebase, $_fetch_parent, + $_before, $_after, $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local, $_prefix, $_no_checkout, $_url, $_verbose, $_commit_url, $_tag, $_merge_info, $_interactive); @@ -258,7 +259,8 @@ my %cmd = ( } ], 'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish", - {} ], + { 'before' => \$_before, + 'after' => \$_after } ], 'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory", { 'merge|m|M' => \$_merge, 'verbose|v' => \$_verbose, @@ -1191,7 +1193,13 @@ sub cmd_find_rev { "$head history\n"; } my $desired_revision = substr($revision_or_hash, 1); - $result = $gs->rev_map_get($desired_revision, $uuid); + if ($_before) { + $result = $gs->find_rev_before($desired_revision, 1); + } elsif ($_after) { + $result = $gs->find_rev_after($desired_revision, 1); + } else { + $result = $gs->rev_map_get($desired_revision, $uuid); + } } else { my (undef, $rev, undef) = cmt_metadata($revision_or_hash); $result = $rev; diff --git a/log-tree.c b/log-tree.c index 8876c736d4..5dc45c4812 100644 --- a/log-tree.c +++ b/log-tree.c @@ -299,26 +299,34 @@ static unsigned int digits_in_number(unsigned int number) return result; } -void get_patch_filename(struct commit *commit, const char *subject, int nr, - const char *suffix, struct strbuf *buf) +void fmt_output_subject(struct strbuf *filename, + const char *subject, + struct rev_info *info) { - int suffix_len = strlen(suffix) + 1; - int start_len = buf->len; - - strbuf_addf(buf, commit || subject ? "%04d-" : "%d", nr); - if (commit || subject) { - int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; - struct pretty_print_context ctx = {0}; - - if (subject) - strbuf_addstr(buf, subject); - else if (commit) - format_commit_message(commit, "%f", buf, &ctx); - - if (max_len < buf->len) - strbuf_setlen(buf, max_len); - strbuf_addstr(buf, suffix); - } + const char *suffix = info->patch_suffix; + int nr = info->nr; + int start_len = filename->len; + int max_len = start_len + FORMAT_PATCH_NAME_MAX - (strlen(suffix) + 1); + + if (0 < info->reroll_count) + strbuf_addf(filename, "v%d-", info->reroll_count); + strbuf_addf(filename, "%04d-%s", nr, subject); + + if (max_len < filename->len) + strbuf_setlen(filename, max_len); + strbuf_addstr(filename, suffix); +} + +void fmt_output_commit(struct strbuf *filename, + struct commit *commit, + struct rev_info *info) +{ + struct pretty_print_context ctx = {0}; + struct strbuf subject = STRBUF_INIT; + + format_commit_message(commit, "%f", &subject, &ctx); + fmt_output_subject(filename, subject.buf, info); + strbuf_release(&subject); } void log_write_email_headers(struct rev_info *opt, struct commit *commit, @@ -387,8 +395,10 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, mime_boundary_leader, opt->mime_boundary); extra_headers = subject_buffer; - get_patch_filename(opt->numbered_files ? NULL : commit, NULL, - opt->nr, opt->patch_suffix, &filename); + if (opt->numbered_files) + strbuf_addf(&filename, "%d", opt->nr); + else + fmt_output_commit(&filename, commit, opt); snprintf(buffer, sizeof(buffer) - 1, "\n--%s%s\n" "Content-Type: text/x-patch;" @@ -671,6 +681,7 @@ void show_log(struct rev_info *opt) ctx.preserve_subject = opt->preserve_subject; ctx.reflog_info = opt->reflog_info; ctx.fmt = opt->commit_format; + ctx.mailmap = opt->mailmap; ctx.color = opt->diffopt.use_color; pretty_print_commit(&ctx, commit, &msgbuf); diff --git a/log-tree.h b/log-tree.h index f5ac238bba..9140f48216 100644 --- a/log-tree.h +++ b/log-tree.h @@ -21,7 +21,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, void load_ref_decorations(int flags); #define FORMAT_PATCH_NAME_MAX 64 -void get_patch_filename(struct commit *commit, const char *subject, int nr, - const char *suffix, struct strbuf *buf); +void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *); +void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *); #endif @@ -235,6 +235,7 @@ int read_mailmap(struct string_list *map, char **repo_abbrev) int err = 0; map->strdup_strings = 1; + map->cmp = strcasecmp; if (!git_mailmap_blob && is_bare_repository()) git_mailmap_blob = "HEAD:.mailmap"; @@ -253,60 +254,95 @@ void clear_mailmap(struct string_list *map) debug_mm("mailmap: cleared\n"); } +/* + * Look for an entry in map that match string[0:len]; string[len] + * does not have to be NUL (but it could be). + */ +static struct string_list_item *lookup_prefix(struct string_list *map, + const char *string, size_t len) +{ + int i = string_list_find_insert_index(map, string, 1); + if (i < 0) { + /* exact match */ + i = -1 - i; + if (!string[len]) + return &map->items[i]; + /* + * that map entry matches exactly to the string, including + * the cruft at the end beyond "len". That is not a match + * with string[0:len] that we are looking for. + */ + } else if (!string[len]) { + /* + * asked with the whole string, and got nothing. No + * matching entry can exist in the map. + */ + return NULL; + } + + /* + * i is at the exact match to an overlong key, or location the + * overlong key would be inserted, which must come after the + * real location of the key if one exists. + */ + while (0 <= --i && i < map->nr) { + int cmp = strncasecmp(map->items[i].string, string, len); + if (cmp < 0) + /* + * "i" points at a key definitely below the prefix; + * the map does not have string[0:len] in it. + */ + break; + else if (!cmp && !map->items[i].string[len]) + /* found it */ + return &map->items[i]; + /* + * otherwise, the string at "i" may be string[0:len] + * followed by a string that sorts later than string[len:]; + * keep trying. + */ + } + return NULL; +} + int map_user(struct string_list *map, - char *email, int maxlen_email, char *name, int maxlen_name) + const char **email, size_t *emaillen, + const char **name, size_t *namelen) { - char *end_of_email; struct string_list_item *item; struct mailmap_entry *me; - char buf[1024], *mailbuf; - int i; - - /* figure out space requirement for email */ - end_of_email = strchr(email, '>'); - if (!end_of_email) { - /* email passed in might not be wrapped in <>, but end with a \0 */ - end_of_email = memchr(email, '\0', maxlen_email); - if (!end_of_email) - return 0; - } - if (end_of_email - email + 1 < sizeof(buf)) - mailbuf = buf; - else - mailbuf = xmalloc(end_of_email - email + 1); - - /* downcase the email address */ - for (i = 0; i < end_of_email - email; i++) - mailbuf[i] = tolower(email[i]); - mailbuf[i] = 0; - - debug_mm("map_user: map '%s' <%s>\n", name, mailbuf); - item = string_list_lookup(map, mailbuf); + + debug_mm("map_user: map '%.*s' <%.*s>\n", + *name, *namelen, *emaillen, *email); + + item = lookup_prefix(map, *email, *emaillen); if (item != NULL) { me = (struct mailmap_entry *)item->util; if (me->namemap.nr) { /* The item has multiple items, so we'll look up on name too */ /* If the name is not found, we choose the simple entry */ - struct string_list_item *subitem = string_list_lookup(&me->namemap, name); + struct string_list_item *subitem; + subitem = lookup_prefix(&me->namemap, *name, *namelen); if (subitem) item = subitem; } } - if (mailbuf != buf) - free(mailbuf); if (item != NULL) { struct mailmap_info *mi = (struct mailmap_info *)item->util; - if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) { + if (mi->name == NULL && mi->email == NULL) { debug_mm("map_user: -- (no simple mapping)\n"); return 0; } - if (maxlen_email && mi->email) - strlcpy(email, mi->email, maxlen_email); - else - *end_of_email = '\0'; - if (maxlen_name && mi->name) - strlcpy(name, mi->name, maxlen_name); - debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : ""); + if (mi->email) { + *email = mi->email; + *emaillen = strlen(*email); + } + if (mi->name) { + *name = mi->name; + *namelen = strlen(*name); + } + debug_mm("map_user: to '%.*s' <.*%s>\n", *namelen, *name, + *emaillen, *email); return 1; } debug_mm("map_user: --\n"); @@ -4,7 +4,7 @@ int read_mailmap(struct string_list *map, char **repo_abbrev); void clear_mailmap(struct string_list *map); -int map_user(struct string_list *mailmap, - char *email, int maxlen_email, char *name, int maxlen_name); +int map_user(struct string_list *map, + const char **email, size_t *emaillen, const char **name, size_t *namelen); #endif diff --git a/merge-file.c b/merge-blobs.c index 7845528e88..57211bccb7 100644 --- a/merge-file.c +++ b/merge-blobs.c @@ -3,7 +3,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "blob.h" -#include "merge-file.h" +#include "merge-blobs.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { @@ -80,7 +80,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) return xdi_diff(f1, f2, &xpp, &xecfg, &ecb); } -void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size) +void *merge_blobs(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size) { void *res = NULL; mmfile_t f1, f2, common; diff --git a/merge-blobs.h b/merge-blobs.h new file mode 100644 index 0000000000..62b569e472 --- /dev/null +++ b/merge-blobs.h @@ -0,0 +1,8 @@ +#ifndef MERGE_BLOBS_H +#define MERGE_BLOBS_H + +#include "blob.h" + +extern void *merge_blobs(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); + +#endif /* MERGE_BLOBS_H */ diff --git a/merge-file.h b/merge-file.h deleted file mode 100644 index 9b3b83ac6c..0000000000 --- a/merge-file.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef MERGE_FILE_H -#define MERGE_FILE_H - -extern void *merge_file(const char *path, struct blob *base, struct blob *our, - struct blob *their, unsigned long *size); - -#endif diff --git a/merge-recursive.c b/merge-recursive.c index d8820604ca..33ba5dc07c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -976,7 +976,7 @@ merge_file_special_markers(struct merge_options *o, return mfi; } -static struct merge_file_info merge_file(struct merge_options *o, +static struct merge_file_info merge_file_one(struct merge_options *o, const char *path, const unsigned char *o_sha, int o_mode, const unsigned char *a_sha, int a_mode, @@ -1166,7 +1166,7 @@ static void conflict_rename_rename_1to2(struct merge_options *o, struct merge_file_info mfi; struct diff_filespec other; struct diff_filespec *add; - mfi = merge_file(o, one->path, + mfi = merge_file_one(o, one->path, one->sha1, one->mode, a->sha1, a->mode, b->sha1, b->mode, @@ -1450,7 +1450,7 @@ static int process_renames(struct merge_options *o, ren1_dst, branch2); if (o->call_depth) { struct merge_file_info mfi; - mfi = merge_file(o, ren1_dst, null_sha1, 0, + mfi = merge_file_one(o, ren1_dst, null_sha1, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_other.sha1, dst_other.mode, branch1, branch2); diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm index 3bbc20a054..3db152155c 100644 --- a/perl/Git/SVN/Editor.pm +++ b/perl/Git/SVN/Editor.pm @@ -145,7 +145,8 @@ sub repo_path { sub url_path { my ($self, $path) = @_; if ($self->{url} =~ m#^https?://#) { - $path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf("%%%02x",ord($1))!eg; + # characters are taken from subversion/libsvn_subr/path.c + $path =~ s#([^~a-zA-Z0-9_./!$&'()*+,-])#uc sprintf("%%%02x",ord($1))#eg; } $self->{url} . '/' . $self->repo_path($path); } @@ -358,12 +359,12 @@ sub T { mode_a => $m->{mode_a}, mode_b => '000000', sha1_a => $m->{sha1_a}, sha1_b => '0' x 40, chg => 'D', file_b => $m->{file_b} - }); + }, $deletions); $self->A({ mode_a => '000000', mode_b => $m->{mode_b}, sha1_a => '0' x 40, sha1_b => $m->{sha1_b}, chg => 'A', file_b => $m->{file_b} - }); + }, $deletions); return; } diff --git a/perl/Git/SVN/Utils.pm b/perl/Git/SVN/Utils.pm index 8b8cf3755c..3d1a0933a2 100644 --- a/perl/Git/SVN/Utils.pm +++ b/perl/Git/SVN/Utils.pm @@ -155,7 +155,7 @@ sub _canonicalize_url_path { my @parts; foreach my $part (split m{/+}, $uri_path) { - $part =~ s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg; + $part =~ s/([^!\$%&'()*+,.\/\w:=\@_`~-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg; push @parts, $part; } @@ -387,56 +387,79 @@ void pp_user_info(const struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding) { + struct strbuf name; + struct strbuf mail; + struct ident_split ident; + int linelen; + char *line_end, *date; + const char *mailbuf, *namebuf; + size_t namelen, maillen; int max_length = 78; /* per rfc2822 */ - char *date; - int namelen; unsigned long time; int tz; if (pp->fmt == CMIT_FMT_ONELINE) return; - date = strchr(line, '>'); - if (!date) + + line_end = strchr(line, '\n'); + if (!line_end) { + line_end = strchr(line, '\0'); + if (!line_end) + return; + } + + linelen = ++line_end - line; + if (split_ident_line(&ident, line, linelen)) return; - namelen = ++date - line; - time = strtoul(date, &date, 10); + + + mailbuf = ident.mail_begin; + maillen = ident.mail_end - ident.mail_begin; + namebuf = ident.name_begin; + namelen = ident.name_end - ident.name_begin; + + if (pp->mailmap) + map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + + strbuf_init(&mail, 0); + strbuf_init(&name, 0); + + strbuf_add(&mail, mailbuf, maillen); + strbuf_add(&name, namebuf, namelen); + + namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */ + time = strtoul(ident.date_begin, &date, 10); tz = strtol(date, NULL, 10); if (pp->fmt == CMIT_FMT_EMAIL) { - char *name_tail = strchr(line, '<'); - int display_name_length; - if (!name_tail) - return; - while (line < name_tail && isspace(name_tail[-1])) - name_tail--; - display_name_length = name_tail - line; strbuf_addstr(sb, "From: "); - if (needs_rfc2047_encoding(line, display_name_length, RFC2047_ADDRESS)) { - add_rfc2047(sb, line, display_name_length, - encoding, RFC2047_ADDRESS); + if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) { + add_rfc2047(sb, name.buf, name.len, + encoding, RFC2047_ADDRESS); max_length = 76; /* per rfc2047 */ - } else if (needs_rfc822_quoting(line, display_name_length)) { + } else if (needs_rfc822_quoting(name.buf, name.len)) { struct strbuf quoted = STRBUF_INIT; - add_rfc822_quoted("ed, line, display_name_length); + add_rfc822_quoted("ed, name.buf, name.len); strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len, -6, 1, max_length); strbuf_release("ed); } else { - strbuf_add_wrapped_bytes(sb, line, display_name_length, - -6, 1, max_length); + strbuf_add_wrapped_bytes(sb, name.buf, name.len, + -6, 1, max_length); } - if (namelen - display_name_length + last_line_length(sb) > max_length) { + if (namelen - name.len + last_line_length(sb) > max_length) strbuf_addch(sb, '\n'); - if (!isspace(name_tail[0])) - strbuf_addch(sb, ' '); - } - strbuf_add(sb, name_tail, namelen - display_name_length); - strbuf_addch(sb, '\n'); + + strbuf_addf(sb, " <%s>\n", mail.buf); } else { - strbuf_addf(sb, "%s: %.*s%.*s\n", what, + strbuf_addf(sb, "%s: %.*s%s <%s>\n", what, (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, - " ", namelen, line); + " ", name.buf, mail.buf); } + + strbuf_release(&mail); + strbuf_release(&name); + switch (pp->fmt) { case CMIT_FMT_MEDIUM: strbuf_addf(sb, "Date: %s\n", show_date(time, tz, pp->date_mode)); @@ -586,7 +609,8 @@ char *logmsg_reencode(const struct commit *commit, return out; } -static int mailmap_name(char *email, int email_len, char *name, int name_len) +static int mailmap_name(const char **email, size_t *email_len, + const char **name, size_t *name_len) { static struct string_list *mail_map; if (!mail_map) { @@ -603,36 +627,26 @@ static size_t format_person_part(struct strbuf *sb, char part, const int placeholder_len = 2; int tz; unsigned long date = 0; - char person_name[1024]; - char person_mail[1024]; struct ident_split s; - const char *name_start, *name_end, *mail_start, *mail_end; + const char *name, *mail; + size_t maillen, namelen; if (split_ident_line(&s, msg, len) < 0) goto skip; - name_start = s.name_begin; - name_end = s.name_end; - mail_start = s.mail_begin; - mail_end = s.mail_end; - - if (part == 'N' || part == 'E') { /* mailmap lookup */ - snprintf(person_name, sizeof(person_name), "%.*s", - (int)(name_end - name_start), name_start); - snprintf(person_mail, sizeof(person_mail), "%.*s", - (int)(mail_end - mail_start), mail_start); - mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name)); - name_start = person_name; - name_end = name_start + strlen(person_name); - mail_start = person_mail; - mail_end = mail_start + strlen(person_mail); - } + name = s.name_begin; + namelen = s.name_end - s.name_begin; + mail = s.mail_begin; + maillen = s.mail_end - s.mail_begin; + + if (part == 'N' || part == 'E') /* mailmap lookup */ + mailmap_name(&mail, &maillen, &name, &namelen); if (part == 'n' || part == 'N') { /* name */ - strbuf_add(sb, name_start, name_end-name_start); + strbuf_add(sb, name, namelen); return placeholder_len; } if (part == 'e' || part == 'E') { /* email */ - strbuf_add(sb, mail_start, mail_end-mail_start); + strbuf_add(sb, mail, maillen); return placeholder_len; } @@ -966,7 +980,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (!end) return 0; - if (!memcmp(begin, "auto,", 5)) { + if (!prefixcmp(begin, "auto,")) { if (!want_color(c->pretty_ctx->color)) return end - placeholder + 1; begin += 5; @@ -1301,7 +1315,7 @@ static void pp_header(const struct pretty_print_context *pp, continue; } - if (!memcmp(line, "parent ", 7)) { + if (!prefixcmp(line, "parent ")) { if (linelen != 48) die("bad parent line in commit"); continue; @@ -1325,11 +1339,11 @@ static void pp_header(const struct pretty_print_context *pp, * FULL shows both authors but not dates. * FULLER shows both authors and dates. */ - if (!memcmp(line, "author ", 7)) { + if (!prefixcmp(line, "author ")) { strbuf_grow(sb, linelen + 80); pp_user_info(pp, "Author", sb, line + 7, encoding); } - if (!memcmp(line, "committer ", 10) && + if (!prefixcmp(line, "committer ") && (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) { strbuf_grow(sb, linelen + 80); pp_user_info(pp, "Commit", sb, line + 10, encoding); diff --git a/revision.c b/revision.c index 95d21e6472..d7562ee500 100644 --- a/revision.c +++ b/revision.c @@ -13,6 +13,7 @@ #include "decorate.h" #include "log-tree.h" #include "string-list.h" +#include "mailmap.h" volatile show_early_output_fn_t show_early_output; @@ -2219,6 +2220,51 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit) return 0; } +static int commit_rewrite_person(struct strbuf *buf, const char *what, struct string_list *mailmap) +{ + char *person, *endp; + size_t len, namelen, maillen; + const char *name; + const char *mail; + struct ident_split ident; + + person = strstr(buf->buf, what); + if (!person) + return 0; + + person += strlen(what); + endp = strchr(person, '\n'); + if (!endp) + return 0; + + len = endp - person; + + if (split_ident_line(&ident, person, len)) + return 0; + + mail = ident.mail_begin; + maillen = ident.mail_end - ident.mail_begin; + name = ident.name_begin; + namelen = ident.name_end - ident.name_begin; + + if (map_user(mailmap, &mail, &maillen, &name, &namelen)) { + struct strbuf namemail = STRBUF_INIT; + + strbuf_addf(&namemail, "%.*s <%.*s>", + (int)namelen, name, (int)maillen, mail); + + strbuf_splice(buf, ident.name_begin - buf->buf, + ident.mail_end - ident.name_begin + 1, + namemail.buf, namemail.len); + + strbuf_release(&namemail); + + return 1; + } + + return 0; +} + static int commit_match(struct commit *commit, struct rev_info *opt) { int retval; @@ -2237,6 +2283,14 @@ static int commit_match(struct commit *commit, struct rev_info *opt) if (buf.len) strbuf_addstr(&buf, commit->buffer); + if (opt->grep_filter.header_list && opt->mailmap) { + if (!buf.len) + strbuf_addstr(&buf, commit->buffer); + + commit_rewrite_person(&buf, "\nauthor ", opt->mailmap); + commit_rewrite_person(&buf, "\ncommitter ", opt->mailmap); + } + /* Append "fake" message parts as needed */ if (opt->show_notes) { if (!buf.len) diff --git a/revision.h b/revision.h index 059bfff812..5da09ee3ef 100644 --- a/revision.h +++ b/revision.h @@ -135,6 +135,7 @@ struct rev_info { const char *mime_boundary; const char *patch_suffix; int numbered_files; + int reroll_count; char *message_id; struct string_list *ref_message_ids; const char *add_signoff; @@ -143,6 +144,7 @@ struct rev_info { const char *subject_prefix; int no_inline; int show_log_size; + struct string_list *mailmap; /* Filter by commit log message */ struct grep_opt grep_filter; diff --git a/run-command.c b/run-command.c index 24eaad5c66..04712191e8 100644 --- a/run-command.c +++ b/run-command.c @@ -249,7 +249,7 @@ static int wait_or_whine(pid_t pid, const char *argv0) * mimics the exit code that a POSIX shell would report for * a program that died from this signal. */ - code -= 128; + code += 128; } else if (WIFEXITED(status)) { code = WEXITSTATUS(status); /* diff --git a/sequencer.c b/sequencer.c index 22604902aa..aef5e8a017 100644 --- a/sequencer.c +++ b/sequencer.c @@ -186,14 +186,15 @@ static int error_dirty_index(struct replay_opts *opts) return -1; } -static int fast_forward_to(const unsigned char *to, const unsigned char *from) +static int fast_forward_to(const unsigned char *to, const unsigned char *from, + int unborn) { struct ref_lock *ref_lock; read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ - ref_lock = lock_any_ref_for_update("HEAD", from, 0); + ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); return write_ref_sha1(ref_lock, to, "cherry-pick"); } @@ -390,7 +391,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; - int res; + int res, unborn = 0; if (opts->no_commit) { /* @@ -402,9 +403,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) if (write_cache_as_tree(head, 0, NULL)) die (_("Your index file is unmerged.")); } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) + unborn = get_sha1("HEAD", head); + if (unborn) + hashcpy(head, EMPTY_TREE_SHA1_BIN); + if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0)) return error_dirty_index(opts); } discard_cache(); @@ -435,8 +437,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) else parent = commit->parents->item; - if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) - return fast_forward_to(commit->object.sha1, head); + if (opts->allow_ff && + ((parent && !hashcmp(parent->object.sha1, head)) || + (!parent && unborn))) + return fast_forward_to(commit->object.sha1, head, unborn); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or diff --git a/string-list.c b/string-list.c index 480173fe6d..aabb25ef4c 100644 --- a/string-list.c +++ b/string-list.c @@ -7,10 +7,11 @@ static int get_entry_index(const struct string_list *list, const char *string, int *exact_match) { int left = -1, right = list->nr; + compare_strings_fn cmp = list->cmp ? list->cmp : strcmp; while (left + 1 < right) { int middle = (left + right) / 2; - int compare = strcmp(string, list->items[middle].string); + int compare = cmp(string, list->items[middle].string); if (compare < 0) right = middle; else if (compare > 0) @@ -96,8 +97,9 @@ void string_list_remove_duplicates(struct string_list *list, int free_util) { if (list->nr > 1) { int src, dst; + compare_strings_fn cmp = list->cmp ? list->cmp : strcmp; for (src = dst = 1; src < list->nr; src++) { - if (!strcmp(list->items[dst - 1].string, list->items[src].string)) { + if (!cmp(list->items[dst - 1].string, list->items[src].string)) { if (list->strdup_strings) free(list->items[src].string); if (free_util) @@ -210,15 +212,20 @@ struct string_list_item *string_list_append(struct string_list *list, list->strdup_strings ? xstrdup(string) : (char *)string); } +/* Yuck */ +static compare_strings_fn compare_for_qsort; + +/* Only call this from inside sort_string_list! */ static int cmp_items(const void *a, const void *b) { const struct string_list_item *one = a; const struct string_list_item *two = b; - return strcmp(one->string, two->string); + return compare_for_qsort(one->string, two->string); } void sort_string_list(struct string_list *list) { + compare_for_qsort = list->cmp ? list->cmp : strcmp; qsort(list->items, list->nr, sizeof(*list->items), cmp_items); } @@ -226,8 +233,10 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list, const char *string) { int i; + compare_strings_fn cmp = list->cmp ? list->cmp : strcmp; + for (i = 0; i < list->nr; i++) - if (!strcmp(string, list->items[i].string)) + if (!cmp(string, list->items[i].string)) return list->items + i; return NULL; } diff --git a/string-list.h b/string-list.h index db1284861a..de6769c92d 100644 --- a/string-list.h +++ b/string-list.h @@ -5,10 +5,14 @@ struct string_list_item { char *string; void *util; }; + +typedef int (*compare_strings_fn)(const char *, const char *); + struct string_list { struct string_list_item *items; unsigned int nr, alloc; unsigned int strdup_strings:1; + compare_strings_fn cmp; /* NULL uses strcmp() */ }; #define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 } diff --git a/t/Makefile b/t/Makefile index 5c6de8169b..1923cc104b 100644 --- a/t/Makefile +++ b/t/Makefile @@ -17,6 +17,7 @@ TEST_LINT ?= test-lint-duplicates test-lint-executable # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) @@ -44,7 +45,7 @@ clean-except-prove-cache: clean: clean-except-prove-cache $(RM) .prove -test-lint: test-lint-duplicates test-lint-executable +test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax test-lint-duplicates: @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ @@ -56,6 +57,9 @@ test-lint-executable: test -z "$$bad" || { \ echo >&2 "non-executable tests:" $$bad; exit 1; } +test-lint-shell-syntax: + @'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) + aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean @@ -88,7 +92,7 @@ test-results: mkdir -p test-results test-results/git-smoke.tar.gz: test-results - $(PERL_PATH) ./harness \ + '$(PERL_PATH_SQ)' ./harness \ --archive="test-results/git-smoke.tar.gz" \ $(T) diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl new file mode 100755 index 0000000000..8b5a71dc05 --- /dev/null +++ b/t/check-non-portable-shell.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +# Test t0000..t9999.sh for non portable shell scripts +# This script can be called with one or more filenames as parameters + +use strict; +use warnings; + +my $exit_code=0; + +sub err { + my $msg = shift; + print "$ARGV:$.: error: $msg: $_\n"; + $exit_code = 1; +} + +while (<>) { + chomp; + /^\s*sed\s+-i/ and err 'sed -i is not portable'; + /^\s*echo\s+-n/ and err 'echo -n is not portable (please use printf)'; + /^\s*declare\s+/ and err 'arrays/declare not portable'; + /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; + /test\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; + # this resets our $. for each file + close ARGV if eof; +} +exit $exit_code; diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 807b8b88e2..43b25137e9 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -206,6 +206,43 @@ test_expect_success 'patterns starting with exclamation' ' attr_check "!f" foo ' +test_expect_success '"**" test' ' + echo "**/f foo=bar" >.gitattributes && + cat <<\EOF >expect && +f: foo: bar +a/f: foo: bar +a/b/f: foo: bar +a/b/c/f: foo: bar +EOF + git check-attr foo -- "f" >actual 2>err && + git check-attr foo -- "a/f" >>actual 2>>err && + git check-attr foo -- "a/b/f" >>actual 2>>err && + git check-attr foo -- "a/b/c/f" >>actual 2>>err && + test_cmp expect actual && + test_line_count = 0 err +' + +test_expect_success '"**" with no slashes test' ' + echo "a**f foo=bar" >.gitattributes && + git check-attr foo -- "f" >actual && + cat <<\EOF >expect && +f: foo: unspecified +af: foo: bar +axf: foo: bar +a/f: foo: unspecified +a/b/f: foo: unspecified +a/b/c/f: foo: unspecified +EOF + git check-attr foo -- "f" >actual 2>err && + git check-attr foo -- "af" >>actual 2>err && + git check-attr foo -- "axf" >>actual 2>err && + git check-attr foo -- "a/f" >>actual 2>>err && + git check-attr foo -- "a/b/f" >>actual 2>>err && + git check-attr foo -- "a/b/c/f" >>actual 2>>err && + test_cmp expect actual && + test_line_count = 0 err +' + test_expect_success 'setup bare' ' git clone --bare . bare.git && cd bare.git diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh index ec6c1b3f8a..5378787e1b 100755 --- a/t/t0024-crlf-archive.sh +++ b/t/t0024-crlf-archive.sh @@ -3,7 +3,12 @@ test_description='respect crlf in git archive' . ./test-lib.sh -UNZIP=${UNZIP:-unzip} +GIT_UNZIP=${GIT_UNZIP:-unzip} + +test_lazy_prereq UNZIP ' + "$GIT_UNZIP" -v + test $? -ne 127 +' test_expect_success setup ' @@ -26,18 +31,11 @@ test_expect_success 'tar archive' ' ' -"$UNZIP" -v >/dev/null 2>&1 -if [ $? -eq 127 ]; then - say "Skipping ZIP test, because unzip was not found" -else - test_set_prereq UNZIP -fi - test_expect_success UNZIP 'zip archive' ' git archive --format=zip HEAD >test.zip && - ( mkdir unzipped && cd unzipped && unzip ../test.zip ) && + ( mkdir unzipped && cd unzipped && "$GIT_UNZIP" ../test.zip ) && test_cmp sample unzipped/sample diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh index d709ecf8df..4969edb314 100755 --- a/t/t1505-rev-parse-last.sh +++ b/t/t1505-rev-parse-last.sh @@ -32,32 +32,24 @@ test_expect_success 'setup' ' # # and 'side' should be the last branch -test_rev_equivalent () { - - git rev-parse "$1" > expect && - git rev-parse "$2" > output && - test_cmp expect output - -} - test_expect_success '@{-1} works' ' - test_rev_equivalent side @{-1} + test_cmp_rev side @{-1} ' test_expect_success '@{-1}~2 works' ' - test_rev_equivalent side~2 @{-1}~2 + test_cmp_rev side~2 @{-1}~2 ' test_expect_success '@{-1}^2 works' ' - test_rev_equivalent side^2 @{-1}^2 + test_cmp_rev side^2 @{-1}^2 ' test_expect_success '@{-1}@{1} works' ' - test_rev_equivalent side@{1} @{-1}@{1} + test_cmp_rev side@{1} @{-1}@{1} ' test_expect_success '@{-2} works' ' - test_rev_equivalent master @{-2} + test_cmp_rev master @{-2} ' test_expect_success '@{-3} fails' ' diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index dc2f0458fd..efb7ebc91f 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -220,4 +220,22 @@ test_expect_success 'pattern matches prefix completely' ' test_cmp expect actual ' +test_expect_success 'ls-files with "**" patterns' ' + cat <<\EOF >expect && +a.1 +one/a.1 +one/two/a.1 +three/a.1 +EOF + git ls-files -o -i --exclude "**/a.1" >actual + test_cmp expect actual +' + + +test_expect_success 'ls-files with "**" patterns and no slashes' ' + : >expect && + git ls-files -o -i --exclude "one**a.1" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh new file mode 100755 index 0000000000..af54c83111 --- /dev/null +++ b/t/t3070-wildmatch.sh @@ -0,0 +1,195 @@ +#!/bin/sh + +test_description='wildmatch tests' + +. ./test-lib.sh + +match() { + if [ $1 = 1 ]; then + test_expect_success "wildmatch: match '$3' '$4'" " + test-wildmatch wildmatch '$3' '$4' + " + else + test_expect_success "wildmatch: no match '$3' '$4'" " + ! test-wildmatch wildmatch '$3' '$4' + " + fi + if [ $2 = 1 ]; then + test_expect_success "fnmatch: match '$3' '$4'" " + test-wildmatch fnmatch '$3' '$4' + " + elif [ $2 = 0 ]; then + test_expect_success "fnmatch: no match '$3' '$4'" " + ! test-wildmatch fnmatch '$3' '$4' + " +# else +# test_expect_success BROKEN_FNMATCH "fnmatch: '$3' '$4'" " +# ! test-wildmatch fnmatch '$3' '$4' +# " + fi +} + +# Basic wildmat features +match 1 1 foo foo +match 0 0 foo bar +match 1 1 '' "" +match 1 1 foo '???' +match 0 0 foo '??' +match 1 1 foo '*' +match 1 1 foo 'f*' +match 0 0 foo '*f' +match 1 1 foo '*foo*' +match 1 1 foobar '*ob*a*r*' +match 1 1 aaaaaaabababab '*ab' +match 1 1 'foo*' 'foo\*' +match 0 0 foobar 'foo\*bar' +match 1 1 'f\oo' 'f\\oo' +match 1 1 ball '*[al]?' +match 0 0 ten '[ten]' +match 0 1 ten '**[!te]' +match 0 0 ten '**[!ten]' +match 1 1 ten 't[a-g]n' +match 0 0 ten 't[!a-g]n' +match 1 1 ton 't[!a-g]n' +match 1 1 ton 't[^a-g]n' +match 1 x 'a]b' 'a[]]b' +match 1 x a-b 'a[]-]b' +match 1 x 'a]b' 'a[]-]b' +match 0 x aab 'a[]-]b' +match 1 x aab 'a[]a-]b' +match 1 1 ']' ']' + +# Extended slash-matching features +match 0 0 'foo/baz/bar' 'foo*bar' +match 0 0 'foo/baz/bar' 'foo**bar' +match 0 1 'foobazbar' 'foo**bar' +match 1 1 'foo/baz/bar' 'foo/**/bar' +match 1 0 'foo/baz/bar' 'foo/**/**/bar' +match 1 0 'foo/b/a/z/bar' 'foo/**/bar' +match 1 0 'foo/b/a/z/bar' 'foo/**/**/bar' +match 1 0 'foo/bar' 'foo/**/bar' +match 1 0 'foo/bar' 'foo/**/**/bar' +match 0 0 'foo/bar' 'foo?bar' +match 0 0 'foo/bar' 'foo[/]bar' +match 0 0 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' +match 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' +match 1 0 'foo' '**/foo' +match 1 x 'XXX/foo' '**/foo' +match 1 0 'bar/baz/foo' '**/foo' +match 0 0 'bar/baz/foo' '*/foo' +match 0 0 'foo/bar/baz' '**/bar*' +match 1 0 'deep/foo/bar/baz' '**/bar/*' +match 0 0 'deep/foo/bar/baz/' '**/bar/*' +match 1 0 'deep/foo/bar/baz/' '**/bar/**' +match 0 0 'deep/foo/bar' '**/bar/*' +match 1 0 'deep/foo/bar/' '**/bar/**' +match 0 0 'foo/bar/baz' '**/bar**' +match 1 0 'foo/bar/baz/x' '*/bar/**' +match 0 0 'deep/foo/bar/baz/x' '*/bar/**' +match 1 0 'deep/foo/bar/baz/x' '**/bar/*/*' + +# Various additional tests +match 0 0 'acrt' 'a[c-c]st' +match 1 1 'acrt' 'a[c-c]rt' +match 0 0 ']' '[!]-]' +match 1 x 'a' '[!]-]' +match 0 0 '' '\' +match 0 x '\' '\' +match 0 x 'XXX/\' '*/\' +match 1 x 'XXX/\' '*/\\' +match 1 1 'foo' 'foo' +match 1 1 '@foo' '@foo' +match 0 0 'foo' '@foo' +match 1 1 '[ab]' '\[ab]' +match 1 1 '[ab]' '[[]ab]' +match 1 x '[ab]' '[[:]ab]' +match 0 x '[ab]' '[[::]ab]' +match 1 x '[ab]' '[[:digit]ab]' +match 1 x '[ab]' '[\[:]ab]' +match 1 1 '?a?b' '\??\?b' +match 1 1 'abc' '\a\b\c' +match 0 0 'foo' '' +match 1 0 'foo/bar/baz/to' '**/t[o]' + +# Character class tests +match 1 x 'a1B' '[[:alpha:]][[:digit:]][[:upper:]]' +match 0 x 'a' '[[:digit:][:upper:][:space:]]' +match 1 x 'A' '[[:digit:][:upper:][:space:]]' +match 1 x '1' '[[:digit:][:upper:][:space:]]' +match 0 x '1' '[[:digit:][:upper:][:spaci:]]' +match 1 x ' ' '[[:digit:][:upper:][:space:]]' +match 0 x '.' '[[:digit:][:upper:][:space:]]' +match 1 x '.' '[[:digit:][:punct:][:space:]]' +match 1 x '5' '[[:xdigit:]]' +match 1 x 'f' '[[:xdigit:]]' +match 1 x 'D' '[[:xdigit:]]' +match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' +match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' +match 1 x '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]' +match 1 x '5' '[a-c[:digit:]x-z]' +match 1 x 'b' '[a-c[:digit:]x-z]' +match 1 x 'y' '[a-c[:digit:]x-z]' +match 0 x 'q' '[a-c[:digit:]x-z]' + +# Additional tests, including some malformed wildmats +match 1 x ']' '[\\-^]' +match 0 0 '[' '[\\-^]' +match 1 x '-' '[\-_]' +match 1 x ']' '[\]]' +match 0 0 '\]' '[\]]' +match 0 0 '\' '[\]]' +match 0 0 'ab' 'a[]b' +match 0 x 'a[]b' 'a[]b' +match 0 x 'ab[' 'ab[' +match 0 0 'ab' '[!' +match 0 0 'ab' '[-' +match 1 1 '-' '[-]' +match 0 0 '-' '[a-' +match 0 0 '-' '[!a-' +match 1 x '-' '[--A]' +match 1 x '5' '[--A]' +match 1 1 ' ' '[ --]' +match 1 1 '$' '[ --]' +match 1 1 '-' '[ --]' +match 0 0 '0' '[ --]' +match 1 x '-' '[---]' +match 1 x '-' '[------]' +match 0 0 'j' '[a-e-n]' +match 1 x '-' '[a-e-n]' +match 1 x 'a' '[!------]' +match 0 0 '[' '[]-a]' +match 1 x '^' '[]-a]' +match 0 0 '^' '[!]-a]' +match 1 x '[' '[!]-a]' +match 1 1 '^' '[a^bc]' +match 1 x '-b]' '[a-]b]' +match 0 0 '\' '[\]' +match 1 1 '\' '[\\]' +match 0 0 '\' '[!\\]' +match 1 1 'G' '[A-\\]' +match 0 0 'aaabbb' 'b*a' +match 0 0 'aabcaa' '*ba*' +match 1 1 ',' '[,]' +match 1 1 ',' '[\\,]' +match 1 1 '\' '[\\,]' +match 1 1 '-' '[,-.]' +match 0 0 '+' '[,-.]' +match 0 0 '-.]' '[,-.]' +match 1 1 '2' '[\1-\3]' +match 1 1 '3' '[\1-\3]' +match 0 0 '4' '[\1-\3]' +match 1 1 '\' '[[-\]]' +match 1 1 '[' '[[-\]]' +match 1 1 ']' '[[-\]]' +match 0 0 '-' '[[-\]]' + +# Test recursion and the abort code (use "wildtest -i" to see iteration counts) +match 1 1 '-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' +match 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' +match 1 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t' +match 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 32fdc9938e..8462be1db6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -29,12 +29,6 @@ Initial setup: . "$TEST_DIRECTORY"/lib-rebase.sh -test_cmp_rev () { - git rev-parse --verify "$1" >expect.rev && - git rev-parse --verify "$2" >actual.rev && - test_cmp expect.rev actual.rev -} - set_fake_editor # WARNING: Modifications to the initial repository can change the SHA ID used diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 34c86e5de6..6f489e20ee 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -100,4 +100,13 @@ test_expect_success 'revert forbidden on dirty working tree' ' ' +test_expect_success 'chery-pick on unborn branch' ' + git checkout --orphan unborn && + git rm --cached -r . && + rm -rf * && + git cherry-pick initial && + git diff --quiet initial && + ! test_cmp_rev initial HEAD +' + test_done diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh index 51ca391e47..373aad623c 100755 --- a/t/t3506-cherry-pick-ff.sh +++ b/t/t3506-cherry-pick-ff.sh @@ -105,4 +105,12 @@ test_expect_success 'cherry pick a root commit with --ff' ' test "$(git rev-parse --verify HEAD)" = "1df192cd8bc58a2b275d842cede4d221ad9000d1" ' +test_expect_success 'chery-pick --ff on unborn branch' ' + git checkout --orphan unborn && + git rm --cached -r . && + rm -rf * && + git cherry-pick --ff first && + test_cmp_rev first HEAD +' + test_done diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index c82f7210c4..223b98433c 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -11,12 +11,6 @@ test_description='test cherry-pick and revert with conflicts . ./test-lib.sh -test_cmp_rev () { - git rev-parse --verify "$1" >expect.rev && - git rev-parse --verify "$2" >actual.rev && - test_cmp expect.rev actual.rev -} - pristine_detach () { git checkout -f "$1^0" && git read-tree -u --reset HEAD && diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh index 340afc760d..4e7136b837 100755 --- a/t/t3508-cherry-pick-many-commits.sh +++ b/t/t3508-cherry-pick-many-commits.sh @@ -5,15 +5,11 @@ test_description='test cherry-picking many commits' . ./test-lib.sh check_head_differs_from() { - head=$(git rev-parse --verify HEAD) && - arg=$(git rev-parse --verify "$1") && - test "$head" != "$arg" + ! test_cmp_rev HEAD "$1" } check_head_equals() { - head=$(git rev-parse --verify HEAD) && - arg=$(git rev-parse --verify "$1") && - test "$head" = "$arg" + test_cmp_rev HEAD "$1" } test_expect_success setup ' diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index b5fb527b2e..7b7a89dbd5 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -24,12 +24,6 @@ pristine_detach () { git clean -d -f -f -q -x } -test_cmp_rev () { - git rev-parse --verify "$1" >expect.rev && - git rev-parse --verify "$2" >actual.rev && - test_cmp expect.rev actual.rev -} - test_expect_success setup ' git config advice.detachedhead false && echo unrelated >unrelated && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index dc0d8ae928..7fa3647514 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -271,6 +271,22 @@ test_expect_success 'multiple files' ' ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch ' +test_expect_success 'reroll count' ' + rm -fr patches && + git format-patch -o patches --cover-letter --reroll-count 4 master..side >list && + ! grep -v "^patches/v4-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects +' + +test_expect_success 'reroll count (-v)' ' + rm -fr patches && + git format-patch -o patches --cover-letter -v4 master..side >list && + ! grep -v "^patches/v4-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects +' + check_threading () { expect="$1" && shift && diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index aae30d97b1..842b7549ec 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -337,6 +337,62 @@ test_expect_success 'Log output (complex mapping)' ' test_cmp expect actual ' +cat >expect <<\EOF +Author: CTO <cto@company.xx> +Author: Santa Claus <santa.claus@northpole.xx> +Author: Santa Claus <santa.claus@northpole.xx> +Author: Other Author <other@author.xx> +Author: Other Author <other@author.xx> +Author: Some Dude <some@dude.xx> +Author: A U Thor <author@example.com> +EOF + +test_expect_success 'Log output with --use-mailmap' ' + git log --use-mailmap | grep Author >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +Author: CTO <cto@company.xx> +Author: Santa Claus <santa.claus@northpole.xx> +Author: Santa Claus <santa.claus@northpole.xx> +Author: Other Author <other@author.xx> +Author: Other Author <other@author.xx> +Author: Some Dude <some@dude.xx> +Author: A U Thor <author@example.com> +EOF + +test_expect_success 'Log output with log.mailmap' ' + git -c log.mailmap=True log | grep Author >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +Author: Santa Claus <santa.claus@northpole.xx> +Author: Santa Claus <santa.claus@northpole.xx> +EOF + +test_expect_success 'Grep author with --use-mailmap' ' + git log --use-mailmap --author Santa | grep Author >actual && + test_cmp expect actual +' +cat >expect <<\EOF +Author: Santa Claus <santa.claus@northpole.xx> +Author: Santa Claus <santa.claus@northpole.xx> +EOF + +test_expect_success 'Grep author with log.mailmap' ' + git -c log.mailmap=True log --author Santa | grep Author >actual && + test_cmp expect actual +' + +>expect + +test_expect_success 'Only grep replaced author with --use-mailmap' ' + git log --use-mailmap --author "<cto@coompany.xx>" >actual && + test_cmp expect actual +' + # git blame cat >expect <<\EOF ^OBJI (A U Thor DATE 1) one diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh index 46c3fe76d3..d0b2a457b8 100755 --- a/t/t4300-merge-tree.sh +++ b/t/t4300-merge-tree.sh @@ -254,4 +254,48 @@ EXPECTED test_cmp expected actual ' +test_expect_success 'turn file to tree' ' + git reset --hard initial && + rm initial-file && + mkdir initial-file && + test_commit "turn-file-to-tree" "initial-file/ONE" "CCC" && + git merge-tree initial initial turn-file-to-tree >actual && + cat >expect <<-\EOF && + added in remote + their 100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 initial-file/ONE + @@ -0,0 +1 @@ + +CCC + removed in remote + base 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file + our 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file + @@ -1 +0,0 @@ + -initial + EOF + test_cmp expect actual +' + +test_expect_success 'turn tree to file' ' + git reset --hard initial && + mkdir dir && + test_commit "add-tree" "dir/path" "AAA" && + test_commit "add-another-tree" "dir/another" "BBB" && + rm -fr dir && + test_commit "make-file" "dir" "CCC" && + git merge-tree add-tree add-another-tree make-file >actual && + cat >expect <<-\EOF && + added in local + our 100644 ba629238ca89489f2b350e196ca445e09d8bb834 dir/another + removed in remote + base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path + @@ -1 +0,0 @@ + -AAA + added in remote + their 100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 dir + @@ -0,0 +1 @@ + +CCC + EOF + test_cmp expect actual +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index ecf00edab2..e7c240fc1f 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -25,32 +25,11 @@ commit id embedding: ' . ./test-lib.sh -UNZIP=${UNZIP:-unzip} GZIP=${GZIP:-gzip} GUNZIP=${GUNZIP:-gzip -d} SUBSTFORMAT=%H%n -check_zip() { - zipfile=$1.zip - listfile=$1.lst - dir=$1 - dir_with_prefix=$dir/$2 - - test_expect_success UNZIP " extract ZIP archive" " - (mkdir $dir && cd $dir && $UNZIP ../$zipfile) - " - - test_expect_success UNZIP " validate filenames" " - (cd ${dir_with_prefix}a && find .) | sort >$listfile && - test_cmp a.lst $listfile - " - - test_expect_success UNZIP " validate file contents" " - diff -r a ${dir_with_prefix}a - " -} - test_expect_success \ 'populate workdir' \ 'mkdir a b c && @@ -201,62 +180,12 @@ test_expect_success \ test_cmp a/substfile2 g/prefix/a/substfile2 ' -$UNZIP -v >/dev/null 2>&1 -if [ $? -eq 127 ]; then - say "Skipping ZIP tests, because unzip was not found" -else - test_set_prereq UNZIP -fi - -test_expect_success \ - 'git archive --format=zip' \ - 'git archive --format=zip HEAD >d.zip' - -check_zip d - -test_expect_success \ - 'git archive --format=zip in a bare repo' \ - '(cd bare.git && git archive --format=zip HEAD) >d1.zip' - -test_expect_success \ - 'git archive --format=zip vs. the same in a bare repo' \ - 'test_cmp d.zip d1.zip' - -test_expect_success 'git archive --format=zip with --output' \ - 'git archive --format=zip --output=d2.zip HEAD && - test_cmp d.zip d2.zip' - -test_expect_success 'git archive with --output, inferring format' ' - git archive --output=d3.zip HEAD && - test_cmp d.zip d3.zip -' - test_expect_success 'git archive with --output, override inferred format' ' git archive --format=tar --output=d4.zip HEAD && test_cmp b.tar d4.zip ' test_expect_success \ - 'git archive --format=zip with prefix' \ - 'git archive --format=zip --prefix=prefix/ HEAD >e.zip' - -check_zip e prefix/ - -test_expect_success 'git archive -0 --format=zip on large files' ' - test_config core.bigfilethreshold 1 && - git archive -0 --format=zip HEAD >large.zip -' - -check_zip large - -test_expect_success 'git archive --format=zip on large files' ' - test_config core.bigfilethreshold 1 && - git archive --format=zip HEAD >large-compressed.zip -' - -check_zip large-compressed - -test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh new file mode 100755 index 0000000000..7cfe9ca3da --- /dev/null +++ b/t/t5003-archive-zip.sh @@ -0,0 +1,131 @@ +#!/bin/sh + +test_description='git archive --format=zip test' + +. ./test-lib.sh +GIT_UNZIP=${GIT_UNZIP:-unzip} + +SUBSTFORMAT=%H%n + +test_lazy_prereq UNZIP ' + "$GIT_UNZIP" -v + test $? -ne 127 +' + +test_lazy_prereq UNZIP_SYMLINKS ' + ( + mkdir unzip-symlinks && + cd unzip-symlinks && + "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip && + test -h symlink + ) +' + +check_zip() { + zipfile=$1.zip + listfile=$1.lst + dir=$1 + dir_with_prefix=$dir/$2 + + test_expect_success UNZIP " extract ZIP archive" ' + (mkdir $dir && cd $dir && "$GIT_UNZIP" ../$zipfile) + ' + + test_expect_success UNZIP " validate filenames" " + (cd ${dir_with_prefix}a && find .) | sort >$listfile && + test_cmp a.lst $listfile + " + + test_expect_success UNZIP " validate file contents" " + diff -r a ${dir_with_prefix}a + " +} + +test_expect_success \ + 'populate workdir' \ + 'mkdir a b c && + echo simple textfile >a/a && + mkdir a/bin && + cp /bin/sh a/bin && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && + printf "A not substituted O" >a/substfile2 && + (p=long_path_to_a_file && cd a && + for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && + echo text >file_with_long_path) +' + +test_expect_success SYMLINKS,UNZIP_SYMLINKS 'add symlink' ' + ln -s a a/symlink_to_a +' + +test_expect_success 'prepare file list' ' + (cd a && find .) | sort >a.lst +' + +test_expect_success \ + 'add ignored file' \ + 'echo ignore me >a/ignored && + echo ignored export-ignore >.git/info/attributes' + +test_expect_success \ + 'add files to repository' \ + 'find a -type f | xargs git update-index --add && + find a -type l | xargs git update-index --add && + treeid=`git write-tree` && + echo $treeid >treeid && + git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \ + git commit-tree $treeid </dev/null)' + +test_expect_success \ + 'create bare clone' \ + 'git clone --bare . bare.git && + cp .git/info/attributes bare.git/info/attributes' + +test_expect_success \ + 'remove ignored file' \ + 'rm a/ignored' + +test_expect_success \ + 'git archive --format=zip' \ + 'git archive --format=zip HEAD >d.zip' + +check_zip d + +test_expect_success \ + 'git archive --format=zip in a bare repo' \ + '(cd bare.git && git archive --format=zip HEAD) >d1.zip' + +test_expect_success \ + 'git archive --format=zip vs. the same in a bare repo' \ + 'test_cmp d.zip d1.zip' + +test_expect_success 'git archive --format=zip with --output' \ + 'git archive --format=zip --output=d2.zip HEAD && + test_cmp d.zip d2.zip' + +test_expect_success 'git archive with --output, inferring format' ' + git archive --output=d3.zip HEAD && + test_cmp d.zip d3.zip +' + +test_expect_success \ + 'git archive --format=zip with prefix' \ + 'git archive --format=zip --prefix=prefix/ HEAD >e.zip' + +check_zip e prefix/ + +test_expect_success 'git archive -0 --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive -0 --format=zip HEAD >large.zip +' + +check_zip large + +test_expect_success 'git archive --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive --format=zip HEAD >large-compressed.zip +' + +check_zip large-compressed + +test_done diff --git a/t/t5003/infozip-symlinks.zip b/t/t5003/infozip-symlinks.zip Binary files differnew file mode 100644 index 0000000000..065728c631 --- /dev/null +++ b/t/t5003/infozip-symlinks.zip diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh index ee06d28649..4435693bb2 100755 --- a/t/t5600-clone-fail-cleanup.sh +++ b/t/t5600-clone-fail-cleanup.sh @@ -37,6 +37,16 @@ test_expect_success \ test_expect_success \ 'successful clone must leave the directory' \ - 'cd bar' + 'test -d bar' + +test_expect_success 'failed clone --separate-git-dir should not leave any directories' ' + mkdir foo/.git/objects.bak/ && + mv foo/.git/objects/* foo/.git/objects.bak/ && + test_must_fail git clone --separate-git-dir gitdir foo worktree && + test_must_fail test -e gitdir && + test_must_fail test -e worktree && + mv foo/.git/objects.bak/* foo/.git/objects/ && + rmdir foo/.git/objects.bak +' test_done diff --git a/t/t5800-remote-testpy.sh b/t/t5800-remote-testpy.sh index 6750961507..1e683d4220 100755 --- a/t/t5800-remote-testpy.sh +++ b/t/t5800-remote-testpy.sh @@ -145,4 +145,25 @@ test_expect_failure 'push new branch with old:new refspec' ' compare_refs clone HEAD server refs/heads/new-refspec ' +test_expect_success 'proper failure checks for fetching' ' + (GIT_REMOTE_TESTGIT_FAILURE=1 && + export GIT_REMOTE_TESTGIT_FAILURE && + cd localclone && + test_must_fail git fetch 2>&1 | \ + grep "Error while running fast-import" + ) +' + +# We sleep to give fast-export a chance to catch the SIGPIPE +test_expect_failure 'proper failure checks for pushing' ' + (GIT_REMOTE_TESTGIT_FAILURE=1 && + export GIT_REMOTE_TESTGIT_FAILURE && + GIT_REMOTE_TESTGIT_SLEEPY=1 && + export GIT_REMOTE_TESTGIT_SLEEPY && + cd localclone && + test_must_fail git push --all 2>&1 | \ + grep "Error while running fast-export" + ) +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 72e28ee535..3e0e15fb3e 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -676,9 +676,7 @@ test_expect_success 'bisect fails if tree is broken on trial commit' ' check_same() { echo "Checking $1 is the same as $2" && - git rev-parse "$1" > expected.same && - git rev-parse "$2" > expected.actual && - test_cmp expected.same expected.actual + test_cmp_rev "$1" "$2" } test_expect_success 'bisect: --no-checkout - start commit bad' ' diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh new file mode 100755 index 0000000000..0da1214bcc --- /dev/null +++ b/t/t7061-wtstatus-ignore.sh @@ -0,0 +1,146 @@ +#!/bin/sh + +test_description='git-status ignored files' + +. ./test-lib.sh + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +?? untracked/ +EOF + +test_expect_success 'status untracked directory with --ignored' ' + echo "ignored" >.gitignore && + mkdir untracked && + : >untracked/ignored && + : >untracked/uncommitted && + git status --porcelain --ignored >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +?? untracked/uncommitted +!! untracked/ignored +EOF + +test_expect_success 'status untracked directory with --ignored -u' ' + git status --porcelain --ignored -u >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! ignored/ +EOF + +test_expect_success 'status ignored directory with --ignore' ' + rm -rf untracked && + mkdir ignored && + : >ignored/uncommitted && + git status --porcelain --ignored >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! ignored/uncommitted +EOF + +test_expect_success 'status ignored directory with --ignore -u' ' + git status --porcelain --ignored -u >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! untracked-ignored/ +EOF + +test_expect_success 'status untracked directory with ignored files with --ignore' ' + rm -rf ignored && + mkdir untracked-ignored && + mkdir untracked-ignored/test && + : >untracked-ignored/ignored && + : >untracked-ignored/test/ignored && + git status --porcelain --ignored >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! untracked-ignored/ignored +!! untracked-ignored/test/ignored +EOF + +test_expect_success 'status untracked directory with ignored files with --ignore -u' ' + git status --porcelain --ignored -u >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +EOF + +test_expect_success 'status ignored tracked directory with --ignore' ' + rm -rf untracked-ignored && + mkdir tracked && + : >tracked/committed && + git add tracked/committed && + git commit -m. && + echo "tracked" >.gitignore && + git status --porcelain --ignored >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +EOF + +test_expect_success 'status ignored tracked directory with --ignore -u' ' + git status --porcelain --ignored -u >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! tracked/ +EOF + +test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' ' + : >tracked/uncommitted && + git status --porcelain --ignored >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +?? .gitignore +?? actual +?? expected +!! tracked/uncommitted +EOF + +test_expect_success 'status ignored tracked directory and uncommitted file with --ignore -u' ' + git status --porcelain --ignored -u >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t7500/add-content-and-comment b/t/t7500/add-content-and-comment new file mode 100755 index 0000000000..c4dccff13a --- /dev/null +++ b/t/t7500/add-content-and-comment @@ -0,0 +1,5 @@ +#!/bin/sh +echo "commit message" >> "$1" +echo "# comment" >> "$1" +exit 0 + diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 1a5cb6983c..b1c7648386 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -4,6 +4,15 @@ test_description='git commit porcelain-ish' . ./test-lib.sh +commit_msg_is () { + expect=commit_msg_is.expect + actual=commit_msg_is.actual + + printf "%s" "$(git log --pretty=format:%s%b -1)" >$actual && + printf "%s" "$1" >$expect && + test_i18ncmp $expect $actual +} + # Arguments: [<prefix] [<commit message>] [<commit options>] check_summary_oneline() { test_tick && @@ -168,7 +177,7 @@ test_expect_success 'verbose respects diff config' ' git config --unset color.diff ' -test_expect_success 'cleanup commit messages (verbatim,-t)' ' +test_expect_success 'cleanup commit messages (verbatim option,-t)' ' echo >>negative && { echo;echo "# text";echo; } >expect && @@ -178,7 +187,7 @@ test_expect_success 'cleanup commit messages (verbatim,-t)' ' ' -test_expect_success 'cleanup commit messages (verbatim,-F)' ' +test_expect_success 'cleanup commit messages (verbatim option,-F)' ' echo >>negative && git commit --cleanup=verbatim -F expect -a && @@ -187,7 +196,7 @@ test_expect_success 'cleanup commit messages (verbatim,-F)' ' ' -test_expect_success 'cleanup commit messages (verbatim,-m)' ' +test_expect_success 'cleanup commit messages (verbatim option,-m)' ' echo >>negative && git commit --cleanup=verbatim -m "$(cat expect)" -a && @@ -196,7 +205,7 @@ test_expect_success 'cleanup commit messages (verbatim,-m)' ' ' -test_expect_success 'cleanup commit messages (whitespace,-F)' ' +test_expect_success 'cleanup commit messages (whitespace option,-F)' ' echo >>negative && { echo;echo "# text";echo; } >text && @@ -207,7 +216,7 @@ test_expect_success 'cleanup commit messages (whitespace,-F)' ' ' -test_expect_success 'cleanup commit messages (strip,-F)' ' +test_expect_success 'cleanup commit messages (strip option,-F)' ' echo >>negative && { echo;echo "# text";echo sample;echo; } >text && @@ -218,7 +227,7 @@ test_expect_success 'cleanup commit messages (strip,-F)' ' ' -test_expect_success 'cleanup commit messages (strip,-F,-e)' ' +test_expect_success 'cleanup commit messages (strip option,-F,-e)' ' echo >>negative && { echo;echo sample;echo; } >text && @@ -231,10 +240,71 @@ echo "sample # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit." >expect -test_expect_success 'cleanup commit messages (strip,-F,-e): output' ' +test_expect_success 'cleanup commit messages (strip option,-F,-e): output' ' test_i18ncmp expect actual ' +test_expect_success 'cleanup commit message (fail on invalid cleanup mode option)' ' + test_must_fail git commit --cleanup=non-existent +' + +test_expect_success 'cleanup commit message (fail on invalid cleanup mode configuration)' ' + test_must_fail git -c commit.cleanup=non-existent commit +' + +test_expect_success 'cleanup commit message (no config and no option uses default)' ' + echo content >>file && + git add file && + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --no-status && + commit_msg_is "commit message" +' + +test_expect_success 'cleanup commit message (option overrides default)' ' + echo content >>file && + git add file && + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --cleanup=whitespace --no-status && + commit_msg_is "commit message # comment" +' + +test_expect_success 'cleanup commit message (config overrides default)' ' + echo content >>file && + git add file && + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --no-status && + commit_msg_is "commit message # comment" +' + +test_expect_success 'cleanup commit message (option overrides config)' ' + echo content >>file && + git add file && + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --cleanup=default && + commit_msg_is "commit message" +' + +test_expect_success 'cleanup commit message (default, -m)' ' + echo content >>file && + git add file && + git commit -m "message #comment " && + commit_msg_is "message #comment" +' + +test_expect_success 'cleanup commit message (whitespace option, -m)' ' + echo content >>file && + git add file && + git commit --cleanup=whitespace --no-status -m "message #comment " && + commit_msg_is "message #comment" +' + +test_expect_success 'cleanup commit message (whitespace config, -m)' ' + echo content >>file && + git add file && + git -c commit.cleanup=whitespace commit --no-status -m "message #comment " && + commit_msg_is "message #comment" +' + test_expect_success 'message shows author when it is not equal to committer' ' echo >>negative && git commit -e -m "sample" -a && diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 8c5979647f..166e75209f 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -160,9 +160,12 @@ test_expect_success 'clone --bare should make a bare repository' ' test_when_finished cleanup_git && ( cd "$git" && - test ! -d .git && - bare=`git config --get core.bare` && - test "$bare" = true + test_path_is_missing .git && + git config --get --bool core.bare true && + git rev-parse --verify refs/remotes/p4/master && + git rev-parse --verify refs/remotes/p4/HEAD && + git rev-parse --verify refs/heads/master && + git rev-parse --verify HEAD ) ' diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index fa40cc8bb5..4f077eeca8 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -27,14 +27,102 @@ test_expect_success 'clone no --git-dir' ' test_must_fail git p4 clone --git-dir=xx //depot ' -test_expect_success 'clone --branch' ' +test_expect_success 'clone --branch should checkout master' ' git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot && test_when_finished cleanup_git && ( cd "$git" && - git ls-files >files && - test_line_count = 0 files && - test_path_is_file .git/refs/remotes/p4/sb + git rev-parse refs/remotes/p4/sb >sb && + git rev-parse refs/heads/master >master && + test_cmp sb master && + git rev-parse HEAD >head && + test_cmp sb head + ) +' + +test_expect_success 'sync when no master branch prints a nice error' ' + test_when_finished cleanup_git && + git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot@2 && + ( + cd "$git" && + test_must_fail git p4 sync 2>err && + grep "Error: no branch refs/remotes/p4/master" err + ) +' + +test_expect_success 'sync --branch builds the full ref name correctly' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init && + + git p4 sync --branch=b1 //depot && + git rev-parse --verify refs/remotes/p4/b1 && + git p4 sync --branch=p4/b2 //depot && + git rev-parse --verify refs/remotes/p4/b2 && + + git p4 sync --import-local --branch=h1 //depot && + git rev-parse --verify refs/heads/p4/h1 && + git p4 sync --import-local --branch=p4/h2 //depot && + git rev-parse --verify refs/heads/p4/h2 && + + git p4 sync --branch=refs/stuff //depot && + git rev-parse --verify refs/stuff + ) +' + +# engages --detect-branches code, which will do filename filtering so +# no sync to either b1 or b2 +test_expect_success 'sync when two branches but no master should noop' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init && + git p4 sync --branch=refs/remotes/p4/b1 //depot@2 && + git p4 sync --branch=refs/remotes/p4/b2 //depot@2 && + git p4 sync && + git show -s --format=%s refs/remotes/p4/b1 >show && + grep "Initial import" show && + git show -s --format=%s refs/remotes/p4/b2 >show && + grep "Initial import" show + ) +' + +test_expect_success 'sync --branch updates specific branch, no detection' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init && + git p4 sync --branch=b1 //depot@2 && + git p4 sync --branch=b2 //depot@2 && + git p4 sync --branch=b2 && + git show -s --format=%s refs/remotes/p4/b1 >show && + grep "Initial import" show && + git show -s --format=%s refs/remotes/p4/b2 >show && + grep "change 3" show + ) +' + +# allows using the refname "p4" as a short name for p4/master +test_expect_success 'clone creates HEAD symbolic reference' ' + git p4 clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git rev-parse --verify refs/remotes/p4/master >master && + git rev-parse --verify p4 >p4 && + test_cmp master p4 + ) +' + +test_expect_success 'clone --branch creates HEAD symbolic reference' ' + git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git rev-parse --verify refs/remotes/p4/sb >sb && + git rev-parse --verify p4 >p4 && + test_cmp sb p4 ) ' @@ -138,9 +226,11 @@ test_expect_success 'clone --use-client-spec' ' View: //depot/sub/... //client2/bus/... EOF ) && - P4CLIENT=client2 && test_when_finished cleanup_git && - git p4 clone --dest="$git" --use-client-spec //depot/... && + ( + P4CLIENT=client2 && + git p4 clone --dest="$git" --use-client-spec //depot/... + ) && ( cd "$git" && test_path_is_file bus/dir/f4 && @@ -153,6 +243,7 @@ test_expect_success 'clone --use-client-spec' ' cd "$git" && git init && git config git-p4.useClientSpec true && + P4CLIENT=client2 && git p4 sync //depot/... && git checkout -b master p4/master && test_path_is_file bus/dir/f4 && @@ -160,6 +251,31 @@ test_expect_success 'clone --use-client-spec' ' ) ' +test_expect_success 'submit works with no p4/master' ' + test_when_finished cleanup_git && + git p4 clone --branch=b1 //depot@1,2 --destination="$git" && + ( + cd "$git" && + test_commit submit-1-branch && + git config git-p4.skipSubmitEdit true && + git p4 submit --branch=b1 + ) +' + +# The sync/rebase part post-submit will engage detect-branches +# machinery which will not do anything in this particular test. +test_expect_success 'submit works with two branches' ' + test_when_finished cleanup_git && + git p4 clone --branch=b1 //depot@1,2 --destination="$git" && + ( + cd "$git" && + git p4 sync --branch=b2 //depot@1,3 && + test_commit submit-2-branches && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 22a4f8fb64..fa62d010f6 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -602,6 +602,13 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# Tests that its two parameters refer to the same revision +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + # Print a sequence of numbers or letters in increasing order. This is # similar to GNU seq(1), but the latter might not be available # everywhere (and does not do letters). It may be used like: diff --git a/t/test-lib.sh b/t/test-lib.sh index 8a12cbb86a..1a6c4ab08c 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -85,7 +85,8 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' .*_TEST PROVE VALGRIND - PERF_AGGREGATING_LATER + UNZIP + PERF_ )); my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); print join("\n", @vars); @@ -128,6 +129,7 @@ fi unset CDPATH unset GREP_OPTIONS +unset UNZIP case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in 1|2|true) diff --git a/t/test-terminal.perl b/t/test-terminal.perl index 10172aee18..1fb373f25b 100755 --- a/t/test-terminal.perl +++ b/t/test-terminal.perl @@ -31,7 +31,7 @@ sub finish_child { } elsif ($? & 127) { my $code = $? & 127; warn "died of signal $code"; - return $code - 128; + return $code + 128; } else { return $? >> 8; } diff --git a/test-wildmatch.c b/test-wildmatch.c new file mode 100644 index 0000000000..e384c8edb1 --- /dev/null +++ b/test-wildmatch.c @@ -0,0 +1,22 @@ +#include "cache.h" +#include "wildmatch.h" + +int main(int argc, char **argv) +{ + int i; + for (i = 2; i < argc; i++) { + if (argv[i][0] == '/') + die("Forward slash is not allowed at the beginning of the\n" + "pattern because Windows does not like it. Use `XXX/' instead."); + else if (!strncmp(argv[i], "XXX/", 4)) + argv[i] += 3; + } + if (!strcmp(argv[1], "wildmatch")) + return !!wildmatch(argv[3], argv[2], 0); + else if (!strcmp(argv[1], "iwildmatch")) + return !!wildmatch(argv[3], argv[2], FNM_CASEFOLD); + else if (!strcmp(argv[1], "fnmatch")) + return !!fnmatch(argv[3], argv[2], FNM_PATHNAME); + else + return 1; +} diff --git a/unpack-trees.c b/unpack-trees.c index 61acc5e564..0e1a196ace 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -837,7 +837,8 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr, { struct cache_entry **cache_end; int dtype = DT_DIR; - int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el); + int ret = is_excluded_from_list(prefix, prefix_len, + basename, &dtype, el); prefix[prefix_len++] = '/'; @@ -856,7 +857,7 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr, * with ret (iow, we know in advance the incl/excl * decision for the entire directory), clear flag here without * calling clear_ce_flags_1(). That function will call - * the expensive excluded_from_list() on every entry. + * the expensive is_excluded_from_list() on every entry. */ return clear_ce_flags_1(cache, cache_end - cache, prefix, prefix_len, @@ -939,7 +940,8 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr, /* Non-directory */ dtype = ce_to_dtype(ce); - ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el); + ret = is_excluded_from_list(ce->name, ce_namelen(ce), + name, &dtype, el); if (ret < 0) ret = defval; if (ret > 0) @@ -1152,7 +1154,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o->dst_index = o->result; done: - free_excludes(&el); + clear_exclude_list(&el); if (o->path_exclude_check) { path_exclude_check_clear(o->path_exclude_check); free(o->path_exclude_check); @@ -1373,7 +1375,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype, return 0; if (o->dir && - path_excluded(o->path_exclude_check, name, -1, &dtype)) + is_path_excluded(o->path_exclude_check, name, -1, &dtype)) /* * ce->name is explicitly excluded, so it is Ok to * overwrite it. diff --git a/upload-pack.c b/upload-pack.c index 6142421ea1..95d83135ae 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -603,6 +603,8 @@ static void receive_needs(void) object = parse_object(sha1); if (!object) die("did not find object for %s", line); + if (object->type != OBJ_COMMIT) + die("invalid shallow object %s", sha1_to_hex(sha1)); object->flags |= CLIENT_SHALLOW; add_object_array(object, NULL, &shallows); continue; diff --git a/wildmatch.c b/wildmatch.c new file mode 100644 index 0000000000..2d3ed84364 --- /dev/null +++ b/wildmatch.c @@ -0,0 +1,235 @@ +/* +** Do shell-style pattern matching for ?, \, [], and * characters. +** It is 8bit clean. +** +** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now <rsalz@bbn.com>. +** +** Modified by Wayne Davison to special-case '/' matching, to make '**' +** work differently than '*', and to fix the character-class code. +*/ + +#include "cache.h" +#include "wildmatch.h" + +typedef unsigned char uchar; + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define FALSE 0 +#define TRUE 1 + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +/* Match pattern "p" against "text" */ +static int dowild(const uchar *p, const uchar *text, int force_lower_case) +{ + uchar p_ch; + const uchar *pattern = p; + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, match_slash, negated; + uchar t_ch, prev_ch; + if ((t_ch = *text) == '\0' && p_ch != '*') + return ABORT_ALL; + if (force_lower_case && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if (force_lower_case && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return NOMATCH; + continue; + case '?': + /* Match anything but '/'. */ + if (t_ch == '/') + return NOMATCH; + continue; + case '*': + if (*++p == '*') { + const uchar *prev_p = p - 2; + while (*++p == '*') {} + if ((prev_p < pattern || *prev_p == '/') && + (*p == '\0' || *p == '/' || + (p[0] == '\\' && p[1] == '/'))) { + /* + * Assuming we already match 'foo/' and are at + * <star star slash>, just assume it matches + * nothing and go ahead match the rest of the + * pattern with the remaining string. This + * helps make foo/<*><*>/bar (<> because + * otherwise it breaks C comment syntax) match + * both foo/bar and foo/a/bar. + */ + if (p[0] == '/' && + dowild(p + 1, text, force_lower_case) == MATCH) + return MATCH; + match_slash = TRUE; + } else + return ABORT_MALFORMED; + } else + match_slash = FALSE; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!match_slash) { + if (strchr((char*)text, '/') != NULL) + return NOMATCH; + } + return MATCH; + } + while (1) { + if (t_ch == '\0') + break; + if ((matched = dowild(p, text, force_lower_case)) != NOMATCH) { + if (!match_slash || matched != ABORT_TO_STARSTAR) + return matched; + } else if (!match_slash && t_ch == '/') + return ABORT_TO_STARSTAR; + t_ch = *++text; + } + return ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal TRUE/FALSE because of "matched" comparison. */ + negated = p_ch == NEGATE_CLASS? TRUE : FALSE; + if (negated) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = FALSE; + do { + if (!p_ch) + return ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return ABORT_ALL; + if (t_ch == p_ch) + matched = TRUE; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = TRUE; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return ABORT_ALL; + i = p - s - 1; + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = TRUE; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = TRUE; + } else /* malformed [:class:] string */ + return ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = TRUE; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == negated || t_ch == '/') + return NOMATCH; + continue; + } + } + + return *text ? NOMATCH : MATCH; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text, int flags) +{ + return dowild((const uchar*)pattern, (const uchar*)text, + flags & FNM_CASEFOLD ? 1 :0); +} diff --git a/wildmatch.h b/wildmatch.h new file mode 100644 index 0000000000..984a38cdc2 --- /dev/null +++ b/wildmatch.h @@ -0,0 +1,9 @@ +/* wildmatch.h */ + +#define ABORT_MALFORMED 2 +#define NOMATCH 1 +#define MATCH 0 +#define ABORT_ALL -1 +#define ABORT_TO_STARSTAR -2 + +int wildmatch(const char *pattern, const char *text, int flags); diff --git a/wt-status.c b/wt-status.c index 2a9658bad4..d7cfe8f31c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -516,7 +516,9 @@ static void wt_status_collect_untracked(struct wt_status *s) if (s->show_ignored_files) { dir.nr = 0; - dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES; + dir.flags = DIR_SHOW_IGNORED; + if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) + dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; fill_directory(&dir, s->pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; |