diff options
192 files changed, 3936 insertions, 1197 deletions
diff --git a/.gitignore b/.gitignore index 14e2b6bde9..fcdd822d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -158,7 +158,7 @@ /gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi -/gitweb/gitweb.min.* +/gitweb/static/gitweb.min.* /test-chmtime /test-ctype /test-date diff --git a/Documentation/RelNotes-1.7.0.7.txt b/Documentation/RelNotes-1.7.0.7.txt new file mode 100644 index 0000000000..d0cb7ca7e2 --- /dev/null +++ b/Documentation/RelNotes-1.7.0.7.txt @@ -0,0 +1,16 @@ +Git v1.7.0.7 Release Notes +========================== + +Fixes since v1.7.0.6 +-------------------- + + * "make NO_CURL=NoThanks install" was broken. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git config --path conf.var" to attempt to expand a variable conf.var + that uses "~/" short-hand segfaulted when $HOME environment variable + was not set. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.1.2.txt b/Documentation/RelNotes-1.7.1.2.txt index 46b6a960c7..61ba14e262 100644 --- a/Documentation/RelNotes-1.7.1.2.txt +++ b/Documentation/RelNotes-1.7.1.2.txt @@ -17,3 +17,12 @@ Fixes since v1.7.1.1 * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option when --keep-dashdash was in effect. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git config --path conf.var" to attempt to expand a variable conf.var + that uses "~/" short-hand segfaulted when $HOME environment variable + was not set. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.2.1.txt b/Documentation/RelNotes-1.7.2.1.txt new file mode 100644 index 0000000000..1103c47a4f --- /dev/null +++ b/Documentation/RelNotes-1.7.2.1.txt @@ -0,0 +1,25 @@ +Git v1.7.2.1 Release Notes +========================== + +Fixes since v1.7.2 +------------------ + + * "git instaweb" wasn't useful when your Apache was installed under a + name other than apache2 (e.g. "httpd"). + + * Similarly, "git web--browse" (invoked by "git help -w") learned that + chrome browser is sometimes called google-chrome. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git config --path conf.var" to attempt to expand a variable conf.var + that uses "~/" short-hand segfaulted when $HOME environment variable + was not set. + + * Documentation on Cygwin failed to build. + + * The error message from "git pull blarg" when 'blarg' is an unknown + remote name has been improved. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.2.2.txt b/Documentation/RelNotes-1.7.2.2.txt new file mode 100644 index 0000000000..71eb6a8b0a --- /dev/null +++ b/Documentation/RelNotes-1.7.2.2.txt @@ -0,0 +1,22 @@ +Git v1.7.2.2 Release Notes +========================== + +Fixes since v1.7.2.1 +-------------------- + + * Object transfer over smart http transport deadlocked the client when + the remote HTTP server returned a failure, instead of erroring it out. + + * git-gui honors custom textconv filters when showing diff and blame; + + * git diff --relative=subdir (without the necessary trailing /) did not + work well; + + * "git diff-files -p --submodule" was recently broken; + + * "git checkout -b n ':/token'" did not work; + + * "git index-pack" (hence "git fetch/clone/pull/push") enabled the object + replacement machinery by mistake (it never should have); + +And other minor fixes and documentation updates. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index eb53e0636e..ece3c77482 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -7,17 +7,16 @@ Checklist (and a short version for the impatient): before committing - do not check in commented out code or unneeded files - the first line of the commit message should be a short - description and should skip the full stop + description (50 characters is the soft limit, see DISCUSSION + in git-commit(1)), and should skip the full stop - the body should provide a meaningful commit message, which: - uses the imperative, present tense: "change", not "changed" or "changes". - includes motivation for the change, and contrasts its implementation with previous behaviour - - if you want your work included in git.git, add a - "Signed-off-by: Your Name <you@example.com>" line to the - commit message (or just use the option "-s" when - committing) to confirm that you agree to the Developer's - Certificate of Origin + - add a "Signed-off-by: Your Name <you@example.com>" line to the + commit message (or just use the option "-s" when committing) + to confirm that you agree to the Developer's Certificate of Origin - make sure that you have tests for the bug you are fixing - make sure that the test suite passes after your commit diff --git a/Documentation/config.txt b/Documentation/config.txt index e75434b3ef..5e86a28f92 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -128,7 +128,7 @@ advice.*:: when writing commit messages. Default: true. commitBeforeMerge:: Advice shown when linkgit:git-merge[1] refuses to - merge to avoid overwritting local changes. + merge to avoid overwriting local changes. Default: true. resolveConflict:: Advices shown by various commands when conflicts @@ -418,7 +418,7 @@ Common unit suffixes of 'k', 'm', or 'g' are supported. core.deltaBaseCacheLimit:: Maximum number of bytes to reserve for caching base objects - that multiple deltafied objects reference. By storing the + that may be referenced by multiple deltified objects. By storing the entire decompressed base objects in a cache Git is able to avoid unpacking and decompressing frequently used base objects multiple times. @@ -563,7 +563,7 @@ not necessarily be the current directory. am.keepcr:: If true, git-am will call git-mailsplit for patches in mbox format with parameter '--keep-cr'. In this case git-mailsplit will - not remove `\r` from lines ending with `\r\n`. Can be overrriden + not remove `\r` from lines ending with `\r\n`. Can be overridden by giving '--no-keep-cr' from the command line. See linkgit:git-am[1], linkgit:git-mailsplit[1]. @@ -826,6 +826,11 @@ diff.renames:: will enable basic rename detection. If set to "copies" or "copy", it will detect copies, as well. +diff.ignoreSubmodules:: + Sets the default value of --ignore-submodules. Note that this + affects only 'git diff' Porcelain, and not lower level 'diff' + commands such as 'git diff-files'. + diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space before each empty output line. Defaults to false. @@ -1002,7 +1007,7 @@ gitcvs.usecrlfattr:: If true, the server will look up the end-of-line conversion attributes for files to determine the '-k' modes to use. If the attributes force git to treat a file as text, - the '-k' mode will be left blank so cvs clients will + the '-k' mode will be left blank so CVS clients will treat it as text. If they suppress text conversion, the file will be set with '-kb' mode, which suppresses any newline munging the client might otherwise do. If the attributes do not allow @@ -1275,7 +1280,9 @@ instaweb.local:: be bound to the local IP (127.0.0.1). instaweb.modulepath:: - The module path for an apache httpd used by linkgit:git-instaweb[1]. + The default module path for linkgit:git-instaweb[1] to use + instead of /usr/lib/apache2/modules. Only used if httpd + is Apache. instaweb.port:: The port number to bind the gitweb httpd to. See @@ -1558,6 +1565,10 @@ receive.denyDeletes:: If set to true, git-receive-pack will deny a ref update that deletes the ref. Use this to prevent such a ref deletion via a push. +receive.denyDeleteCurrent:: + If set to true, git-receive-pack will deny a ref update that + deletes the currently checked out branch of a non-bare repository. + receive.denyCurrentBranch:: If set to true or "refuse", git-receive-pack will deny a ref update to the currently checked out branch of a non-bare repository. @@ -1743,6 +1754,19 @@ submodule.<name>.update:: URL and other values found in the `.gitmodules` file. See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. +submodule.<name>.ignore:: + Defines under what circumstances "git status" and the diff family show + a submodule as modified. When set to "all", it will never be considered + modified, "dirty" will ignore all changes to the submodules work tree and + takes only differences between the HEAD of the submodule and the commit + recorded in the superproject into account. "untracked" will additionally + let submodules with modified tracked files in their work tree show up. + Using "none" (the default when this option is not set) also shows + submodules that have untracked files in their work tree as changed. + This setting overrides any setting made in .gitmodules for this submodule, + both settings can be overriden on the command line by using the + "--ignore-submodules" option. + tar.umask:: This variable can be used to restrict the permission bits of tar archive entries. The default is 0002, which turns off the diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 2371262b10..4656a97e60 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -206,10 +206,29 @@ endif::git-format-patch[] the diff-patch output format. Non default number of digits can be specified with `--abbrev=<n>`. --B:: - Break complete rewrite changes into pairs of delete and create. - --M:: +-B[<n>][/<m>]:: + Break complete rewrite changes into pairs of delete and + create. This serves two purposes: ++ +It affects the way a change that amounts to a total rewrite of a file +not as a series of deletion and insertion mixed together with a very +few lines that happen to match textually as the context, but as a +single deletion of everything old followed by a single insertion of +everything new, and the number `m` controls this aspect of the -B +option (defaults to 60%). `-B/70%` specifies that less than 30% of the +original should remain in the result for git to consider it a total +rewrite (i.e. otherwise the resulting patch will be a series of +deletion and insertion mixed together with context lines). ++ +When used with -M, a totally-rewritten file is also considered as the +source of a rename (usually -M only considers a file that disappeared +as the source of a rename), and the number `n` controls this aspect of +the -B option (defaults to 50%). `-B20%` specifies that a change with +addition and deletion compared to 20% or more of the file's size are +eligible for being picked up as a possible source of a rename to +another file. + +-M[<n>]:: ifndef::git-log[] Detect renames. endif::git-log[] @@ -218,9 +237,15 @@ ifdef::git-log[] For following files across renames while traversing history, see `--follow`. endif::git-log[] + If `n` is specified, it is a is a threshold on the similarity + index (i.e. amount of addition/deletions compared to the + file's size). For example, `-M90%` means git should consider a + delete/add pair to be a rename if more than 90% of the file + hasn't changed. --C:: +-C[<n>]:: Detect copies as well as renames. See also `--find-copies-harder`. + If `n` is specified, it has the same meaning as for `-M<n>`. ifndef::git-format-patch[] --diff-filter=[ACDMRTUXB*]:: @@ -330,7 +355,11 @@ endif::git-format-patch[] --ignore-submodules[=<when>]:: Ignore changes to submodules in the diff generation. <when> can be - either "untracked", "dirty" or "all", which is the default. When + either "none", "untracked", "dirty" or "all", which is the default + Using "none" will consider the submodule modified when it either contains + untracked or modified files or its HEAD differs from the commit recorded + in the superproject and can be used to override any settings of the + 'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When "untracked" is used submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified content). Using "dirty" ignores all changes to the work tree of submodules, diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 8463439ac5..4a74b23d40 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -26,6 +26,10 @@ with the `--cache` option the patch is only applied to the index. Without these options, the command applies the patch only to files, and does not require them to be in a git repository. +This command applies the patch but does not create a commit. Use +linkgit:git-am[1] to create commits from patches generated by +linkgit:git-format-patch[1] and/or received by email. + OPTIONS ------- <patch>...:: @@ -242,6 +246,12 @@ If `--index` is not specified, then the submodule commits in the patch are ignored and only the absence or presence of the corresponding subdirectory is checked and (if possible) updated. + +SEE ALSO +-------- +linkgit:git-am[1]. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index 86b3015c13..efbe3790bb 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -971,7 +971,7 @@ logical change in each commit. The smaller the changes in your commit, the most effective "git bisect" will be. And you will probably need "git bisect" less in the first place, as small changes are easier to review even if they are -only reviewed by the commiter. +only reviewed by the committer. Another good idea is to have good commit messages. They can be very helpful to understand why some changes were made. diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 1bacd2e104..66e570113a 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [-m] [<branch>] -'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>] +'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... 'git checkout' --patch [<tree-ish>] [--] [<paths>...] @@ -21,7 +21,7 @@ also update `HEAD` to set the specified branch as the current branch. 'git checkout' [<branch>]:: -'git checkout' -b <new branch> [<start point>]:: +'git checkout' -b|-B <new_branch> [<start point>]:: This form switches branches by updating the index, working tree, and HEAD to reflect the specified branch. @@ -31,6 +31,17 @@ were called and then checked out; in this case you can use the `--track` or `--no-track` options, which will be passed to 'git branch'. As a convenience, `--track` without `-b` implies branch creation; see the description of `--track` below. ++ +If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it +is reset. This is the transactional equivalent of ++ +------------ +$ git branch -f <branch> [<start point>] +$ git checkout <branch> +------------ ++ +that is to say, the branch is not reset/created unless "git checkout" is +successful. 'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: @@ -75,6 +86,12 @@ entries; instead, unmerged entries are ignored. Create a new branch named <new_branch> and start it at <start_point>; see linkgit:git-branch[1] for details. +-B:: + Creates the branch <new_branch> and start it at <start_point>; + if it already exists, then reset it to <start_point>. This is + equivalent to running "git branch" with "-f"; see + linkgit:git-branch[1] for details. + -t:: --track:: When creating a new branch, set up "upstream" configuration. See diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index a81cb6c280..60e38e6e27 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... DESCRIPTION ----------- @@ -45,6 +45,12 @@ OPTIONS Be quiet, only report errors, but not the files that are successfully removed. +-e <pattern>:: +--exclude=<pattern>:: + Specify special exceptions to not be cleaned. Each <pattern> is + the same form as in $GIT_DIR/info/excludes and this option can be + given multiple times. + -x:: Don't use the ignore rules. This allows removing all untracked files, including build products. This can be used (possibly in diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index c28603ecf5..42fb1f57b2 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -130,11 +130,11 @@ OPTIONS Usually recording a commit that has the exact same tree as its sole parent commit is a mistake, and the command prevents you from making such a commit. This option bypasses the safety, and - is primarily for use by foreign scm interface scripts. + is primarily for use by foreign SCM interface scripts. --allow-empty-message:: Like --allow-empty this command is primarily for use by foreign - scm interface scripts. It allows you to create a commit with an + SCM interface scripts. It allows you to create a commit with an empty commit message without using plumbing commands like linkgit:git-commit-tree[1]. diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 8bcd875a67..608cd63fc3 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -188,7 +188,7 @@ ISSUES ------ Problems related to timestamps: - * If timestamps of commits in the cvs repository are not stable enough + * If timestamps of commits in the CVS repository are not stable enough to be used for ordering commits changes may show up in the wrong order. * If any files were ever "cvs import"ed more than once (e.g., import of @@ -201,7 +201,7 @@ Problems related to branches: * Branches on which no commits have been made are not imported. * All files from the branching point are added to a branch even if - never added in cvs. + never added in CVS. * This applies to files added to the source branch *after* a daughter branch was created: if previously no commit was made on the daughter branch they will erroneously be added to the daughter branch in git. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 7004dd2dec..f4472c61db 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -366,8 +366,8 @@ CRLF Line Ending Conversions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default the server leaves the '-k' mode blank for all files, -which causes the cvs client to treat them as a text files, subject -to crlf conversion on some platforms. +which causes the CVS client to treat them as a text files, subject +to end-of-line conversion on some platforms. You can make the server use the end-of-line conversion attributes to set the '-k' modes for files by setting the `gitcvs.usecrlfattr` diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 77a0a2481a..966ba4f213 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -482,9 +482,11 @@ External data format:: 'M' SP <mode> SP <dataref> SP <path> LF .... + -Here `<dataref>` can be either a mark reference (`:<idnum>`) +Here usually `<dataref>` must be either a mark reference (`:<idnum>`) set by a prior `blob` command, or a full 40-byte SHA-1 of an -existing Git blob object. +existing Git blob object. If `<mode>` is `040000`` then +`<dataref>` must be the full 40-byte SHA-1 of an existing +Git tree object or a mark reference set with `--import-marks`. Inline data format:: The data content for the file has not been supplied yet. @@ -509,6 +511,8 @@ in octal. Git only supports the following modes: * `160000`: A gitlink, SHA-1 of the object refers to a commit in another repository. Git links can only be specified by SHA or through a commit mark. They are used to implement submodules. +* `040000`: A subdirectory. Subdirectories can only be specified by + SHA or through a tree mark set with `--import-marks`. In both formats `<path>` is the complete path of the file to be added (if not already existing) or modified (if already existing). diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index 6904739a48..51edeecbe5 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -49,7 +49,7 @@ OPTIONS --no-filters:: Hash the contents as is, ignoring any input filter that would - have been chosen by the attributes mechanism, including crlf + have been chosen by the attributes mechanism, including the end-of-line conversion. If the file is read from standard input then this is always implied, unless the --path option is given. diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 2c3c4d2994..e70cea9320 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -49,15 +49,18 @@ OPTIONS linkgit:git-web--browse[1] for more information about this. If the script fails, the URL will be printed to stdout. +start:: --start:: Start the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance. +stop:: --stop:: Stop the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance, nor does it close the browser. +restart:: --restart:: Restart the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index e970664fe1..c213bdbdc5 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -55,6 +55,9 @@ OPTIONS paths. With this, the full diff is shown for commits that touch the specified paths; this means that "<path>..." limits only commits, and doesn't limit diff for those commits. ++ +Note that this affects all diff-based output types, e.g. those +produced by --stat etc. --log-size:: Before the log message print out its size in bytes. Intended diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 3521637b58..a7c8174d01 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -106,8 +106,16 @@ OPTIONS with `-s` or `-u` options does not make any sense. -t:: - Identify the file status with the following tags (followed by - a space) at the start of each line: + This feature is semi-deprecated. For scripting purpose, + linkgit:git-status[1] `--porcelain` and + linkgit:git-diff-files[1] `--name-status` are almost always + superior alternatives, and users should look at + linkgit:git-status[1] `--short` or linkgit:git-diff[1] + `--name-status` for more user-friendly alternatives. ++ +This option identifies the file status with the following tags (followed by +a space) at the start of each line: + H:: cached S:: skip-worktree M:: unmerged @@ -132,6 +140,12 @@ OPTIONS lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. +--debug:: + After each line that describes a file, add more data about its + cache entry. This is intended to show as much information as + possible for manual inspection; the exact format may change at + any time. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 5540af5d16..2981d8c5ef 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -129,10 +129,12 @@ OPTIONS is taken to be in `refs/notes/` if it is not qualified. -n:: +--dry-run:: Do not remove anything; just report the object names whose notes would be removed. -v:: +--verbose:: Report all object names whose notes are removed. diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 15cfb7a8dc..4d673a5686 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -31,10 +31,12 @@ OPTIONS ------- -n:: +--dry-run:: Do not remove anything; just report what it would remove. -v:: +--verbose:: Report all removed objects. \--:: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index ab4de10358..c50f7dcb89 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -8,29 +8,72 @@ git-pull - Fetch from and merge with another repository or a local branch SYNOPSIS -------- -'git pull' <options> <repository> <refspec>... +'git pull' [options] [<repository> [<refspec>...]] DESCRIPTION ----------- -Runs 'git fetch' with the given parameters, and calls 'git merge' -to merge the retrieved head(s) into the current branch. -With `--rebase`, calls 'git rebase' instead of 'git merge'. -Note that you can use `.` (current directory) as the -<repository> to pull from the local repository -- this is useful -when merging local branches into the current branch. +Incorporates changes from a remote repository into the current +branch. In its default mode, `git pull` is shorthand for +`git fetch` followed by `git merge FETCH_HEAD`. -Also note that options meant for 'git pull' itself and underlying -'git merge' must be given before the options meant for 'git fetch'. +More precisely, 'git pull' runs 'git fetch' with the given +parameters and calls 'git merge' to merge the retrieved branch +heads into the current branch. +With `--rebase`, it runs 'git rebase' instead of 'git merge'. -*Warning*: Running 'git pull' (actually, the underlying 'git merge') +<repository> should be the name of a remote repository as +passed to linkgit:git-fetch[1]. <refspec> can name an +arbitrary remote ref (for example, the name of a tag) or even +a collection of refs with corresponding remote tracking branches +(e.g., refs/heads/*:refs/remotes/origin/*), but usually it is +the name of a branch in the remote repository. + +Default values for <repository> and <branch> are read from the +"remote" and "merge" configuration for the current branch +as set by linkgit:git-branch[1] `--track`. + +Assume the following history exists and the current branch is +"`master`": + +------------ + A---B---C master on origin + / + D---E---F---G master +------------ + +Then "`git pull`" will fetch and replay the changes from the remote +`master` branch since it diverged from the local `master` (i.e., `E`) +until its current commit (`C`) on top of `master` and record the +result in a new commit along with the names of the two parent commits +and a log message from the user describing the changes. + +------------ + A---B---C remotes/origin/master + / \ + D---E---F---G---H master +------------ + +See linkgit:git-merge[1] for details, including how conflicts +are presented and handled. + +In git 1.7.0 or later, to cancel a conflicting merge, use +`git reset --merge`. *Warning*: In older versions of git, running 'git pull' with uncommitted changes is discouraged: while possible, it leaves you -in a state that is hard to back out of in the case of a conflict. +in a state that may be hard to back out of in the case of a conflict. + +If any of the remote changes overlap with local uncommitted changes, +the merge will be automatically cancelled and the work tree untouched. +It is generally best to get any local changes in working order before +pulling or stash them away with linkgit:git-stash[1]. OPTIONS ------- +Options meant for 'git pull' itself and the underlying 'git merge' +must be given before the options meant for 'git fetch'. + -q:: --quiet:: This is passed to both underlying git-fetch to squelch reporting of diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index b68abff28a..658ff2ff67 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -200,16 +200,29 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `<old>..<new>` in most cases, and - `<old>...<new>` for forced non-fast-forward updates). For a - failed update, more details are given for the failure. - The string `rejected` indicates that git did not try to send the - ref at all (typically because it is not a fast-forward). The - string `remote rejected` indicates that the remote end refused - the update; this rejection is typically caused by a hook on the - remote side. The string `remote failure` indicates that the - remote end did not report the successful update of the ref - (perhaps because of a temporary error on the remote side, a - break in the network connection, or other transient error). + `<old>...<new>` for forced non-fast-forward updates). ++ +For a failed update, more details are given: ++ +-- +rejected:: + Git did not try to send the ref at all, typically because it + is not a fast-forward and you did not force the update. + +remote rejected:: + The remote end refused the update. Usually caused by a hook + on the remote side, or because the remote repository has one + of the following safety options in effect: + `receive.denyCurrentBranch` (for pushes to the checked out + branch), `receive.denyNonFastForwards` (for forced + non-fast-forward updates), `receive.denyDeletes` or + `receive.denyDeleteCurrent`. See linkgit:git-config[1]. + +remote failure:: + The remote end did not report the successful update of the ref, + perhaps because of a temporary error on the remote side, a + break in the network connection, or other transient error. +-- from:: The name of the local ref being pushed, minus its diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index f6037c4f6a..2e78da448f 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -412,6 +412,13 @@ turn `core.sparseCheckout` on in order to have sparse checkout support. +BUGS +---- +In order to match a directory with $GIT_DIR/info/sparse-checkout, +trailing slash must be used. The form without trailing slash, while +works with .gitignore, does not work with sparse checkout. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index be23ad2359..b4314568f5 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -250,6 +250,13 @@ on top of the <upstream> branch using the given strategy, using the 'ours' strategy simply discards all patches from the <branch>, which makes little sense. +-X <strategy-option>:: +--strategy-option=<strategy-option>:: + Pass the <strategy-option> through to the merge strategy. + This implies `\--merge` and, if no strategy has been + specified, `-s recursive`. Note the reversal of 'ours' and + 'theirs' as noted in above for the `-m` option. + -q:: --quiet:: Be quiet. Implies --no-stat. diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt index 19335fddae..400f61f6e2 100644 --- a/Documentation/git-request-pull.txt +++ b/Documentation/git-request-pull.txt @@ -7,7 +7,7 @@ git-request-pull - Generates a summary of pending changes SYNOPSIS -------- -'git request-pull' <start> <url> [<end>] +'git request-pull' [-p] <start> <url> [<end>] DESCRIPTION ----------- @@ -17,6 +17,9 @@ the given URL in the generated summary. OPTIONS ------- +-p:: + Show patch text + <start>:: Commit to start at. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 645f0c1748..9cf31485fe 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,40 +8,50 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git reset' [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... 'git reset' --patch [<commit>] [--] [<paths>...] +'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION ----------- -Sets the current head to the specified commit and optionally resets the -index and working tree to match. - -This command is useful if you notice some small error in a recent -commit (or set of commits) and want to redo that part without showing -the undo in the history. - -If you want to undo a commit other than the latest on a branch, -linkgit:git-revert[1] is your friend. - -The second and third forms with 'paths' and/or --patch are used to -revert selected paths in the index from a given commit, without moving -HEAD. - +In the first and second form, copy entries from <commit> to the index. +In the third form, set the current branch to <commit>, optionally +modifying index and worktree to match. The <commit> defaults to HEAD +in all forms. + +'git reset' [-q] [<commit>] [--] <paths>...:: + This form resets the index entries for all <paths> to their + state at the <commit>. (It does not affect the worktree, nor + the current branch.) ++ +This means that `git reset <paths>` is the opposite of `git add +<paths>`. -OPTIONS -------- ---mixed:: - Resets the index but not the working tree (i.e., the changed files - are preserved but not marked for commit) and reports what has not - been updated. This is the default action. +'git reset' --patch|-p [<commit>] [--] [<paths>...]:: + Interactively select hunks in the difference between the index + and <commit> (defaults to HEAD). The chosen hunks are applied + in reverse to the index. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). +'git reset' [--<mode>] [<commit>]:: + This form points the current branch to <commit> and then + updates index and working tree according to <mode>, which must + be one of the following: ++ +-- --soft:: Does not touch the index file nor the working tree at all, but requires them to be in a good order. This leaves all your changed files "Changes to be committed", as 'git status' would put it. +--mixed:: + Resets the index but not the working tree (i.e., the changed files + are preserved but not marked for commit) and reports what has not + been updated. This is the default action. + --hard:: Matches the working tree and index to that of the tree being switched to. Any changes to tracked files in the working tree @@ -59,132 +69,46 @@ OPTIONS the given commit. If a file that is different between the current commit and the given commit has local changes, reset is aborted. +-- --p:: ---patch:: - Interactively select hunks in the difference between the index - and <commit> (defaults to HEAD). The chosen hunks are applied - in reverse to the index. -+ -This means that `git reset -p` is the opposite of `git add -p` (see -linkgit:git-add[1]). +If you want to undo a commit other than the latest on a branch, +linkgit:git-revert[1] is your friend. + + +OPTIONS +------- -q:: --quiet:: Be quiet, only report errors. -<commit>:: - Commit to make the current HEAD. If not given defaults to HEAD. - -DISCUSSION ----------- -The tables below show what happens when running: - ----------- -git reset --option target ----------- - -to reset the HEAD to another commit (`target`) with the different -reset options depending on the state of the files. - -In these tables, A, B, C and D are some different states of a -file. For example, the first line of the first table means that if a -file is in state A in the working tree, in state B in the index, in -state C in HEAD and in state D in the target, then "git reset --soft -target" will put the file in state A in the working tree, in state B -in the index and in state D in HEAD. - - working index HEAD target working index HEAD - ---------------------------------------------------- - A B C D --soft A B D - --mixed A D D - --hard D D D - --merge (disallowed) - --keep (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - A B C C --soft A B C - --mixed A C C - --hard C C C - --merge (disallowed) - --keep A C C - - working index HEAD target working index HEAD - ---------------------------------------------------- - B B C D --soft B B D - --mixed B D D - --hard D D D - --merge D D D - --keep (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - B B C C --soft B B C - --mixed B C C - --hard C C C - --merge C C C - --keep B C C - - working index HEAD target working index HEAD - ---------------------------------------------------- - B C C D --soft B C D - --mixed B D D - --hard D D D - --merge (disallowed) - --keep (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - B C C C --soft B C C - --mixed B C C - --hard C C C - --merge B C C - --keep B C C - -"reset --merge" is meant to be used when resetting out of a conflicted -merge. Any mergy operation guarantees that the work tree file that is -involved in the merge does not have local change wrt the index before -it starts, and that it writes the result out to the work tree. So if -we see some difference between the index and the target and also -between the index and the work tree, then it means that we are not -resetting out from a state that a mergy operation left after failing -with a conflict. That is why we disallow --merge option in this case. - -"reset --keep" is meant to be used when removing some of the last -commits in the current branch while keeping changes in the working -tree. If there could be conflicts between the changes in the commit we -want to remove and the changes in the working tree we want to keep, -the reset is disallowed. That's why it is disallowed if there are both -changes between the working tree and HEAD, and between HEAD and the -target. To be safe, it is also disallowed when there are unmerged -entries. - -The following tables show what happens when there are unmerged -entries: - - working index HEAD target working index HEAD - ---------------------------------------------------- - X U A B --soft (disallowed) - --mixed X B B - --hard B B B - --merge B B B - --keep (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - X U A A --soft (disallowed) - --mixed X A A - --hard A A A - --merge A A A - --keep (disallowed) - -X means any state and U means an unmerged index. - -Examples +EXAMPLES -------- +Undo add:: ++ +------------ +$ edit <1> +$ git add frotz.c filfre.c +$ mailx <2> +$ git reset <3> +$ git pull git://info.example.com/ nitfol <4> +------------ ++ +<1> You are happily working on something, and find the changes +in these files are in good order. You do not want to see them +when you run "git diff", because you plan to work on other files +and changes with these files are distracting. +<2> Somebody asks you to pull, and the changes sounds worthy of merging. +<3> However, you already dirtied the index (i.e. your index does +not match the HEAD commit). But you know the pull you are going +to make does not affect frotz.c nor filfre.c, so you revert the +index changes for these two files. Your changes in working tree +remain there. +<4> Then you can pull and merge, leaving frotz.c and filfre.c +changes still in the working tree. + Undo a commit and redo:: + ------------ @@ -204,19 +128,6 @@ edit the message further, you can give -C option instead. + See also the --amend option to linkgit:git-commit[1]. -Undo commits permanently:: -+ ------------- -$ git commit ... -$ git reset --hard HEAD~3 <1> ------------- -+ -<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad -and you do not want to ever see them again. Do *not* do this if -you have already given these commits to somebody else. (See the -"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for -the implications of doing so.) - Undo a commit, making it a topic branch:: + ------------ @@ -232,28 +143,18 @@ current HEAD. <2> Rewind the master branch to get rid of those three commits. <3> Switch to "topic/wip" branch and keep working. -Undo add:: +Undo commits permanently:: + ------------ -$ edit <1> -$ git add frotz.c filfre.c -$ mailx <2> -$ git reset <3> -$ git pull git://info.example.com/ nitfol <4> +$ git commit ... +$ git reset --hard HEAD~3 <1> ------------ + -<1> You are happily working on something, and find the changes -in these files are in good order. You do not want to see them -when you run "git diff", because you plan to work on other files -and changes with these files are distracting. -<2> Somebody asks you to pull, and the changes sounds worthy of merging. -<3> However, you already dirtied the index (i.e. your index does -not match the HEAD commit). But you know the pull you are going -to make does not affect frotz.c nor filfre.c, so you revert the -index changes for these two files. Your changes in working tree -remain there. -<4> Then you can pull and merge, leaving frotz.c and filfre.c -changes still in the working tree. +<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad +and you do not want to ever see them again. Do *not* do this if +you have already given these commits to somebody else. (See the +"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for +the implications of doing so.) Undo a merge or pull:: + @@ -355,7 +256,7 @@ Keep changes in working tree while discarding some previous commits:: Suppose you are working on something and you commit it, and then you continue working a bit more, but now you think that what you have in your working tree should be in another branch that has nothing to do -with what you commited previously. You can start a new branch and +with what you committed previously. You can start a new branch and reset it while keeping the changes in your work tree. + ------------ @@ -376,6 +277,114 @@ $ git reset --keep start <3> <3> But you can use "reset --keep" to remove the unwanted commit after you switched to "branch2". + +DISCUSSION +---------- + +The tables below show what happens when running: + +---------- +git reset --option target +---------- + +to reset the HEAD to another commit (`target`) with the different +reset options depending on the state of the files. + +In these tables, A, B, C and D are some different states of a +file. For example, the first line of the first table means that if a +file is in state A in the working tree, in state B in the index, in +state C in HEAD and in state D in the target, then "git reset --soft +target" will put the file in state A in the working tree, in state B +in the index and in state D in HEAD. + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C D --soft A B D + --mixed A D D + --hard D D D + --merge (disallowed) + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C C --soft A B C + --mixed A C C + --hard C C C + --merge (disallowed) + --keep A C C + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C D --soft B B D + --mixed B D D + --hard D D D + --merge D D D + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C C --soft B B C + --mixed B C C + --hard C C C + --merge C C C + --keep B C C + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C D --soft B C D + --mixed B D D + --hard D D D + --merge (disallowed) + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C C --soft B C C + --mixed B C C + --hard C C C + --merge B C C + --keep B C C + +"reset --merge" is meant to be used when resetting out of a conflicted +merge. Any mergy operation guarantees that the work tree file that is +involved in the merge does not have local change wrt the index before +it starts, and that it writes the result out to the work tree. So if +we see some difference between the index and the target and also +between the index and the work tree, then it means that we are not +resetting out from a state that a mergy operation left after failing +with a conflict. That is why we disallow --merge option in this case. + +"reset --keep" is meant to be used when removing some of the last +commits in the current branch while keeping changes in the working +tree. If there could be conflicts between the changes in the commit we +want to remove and the changes in the working tree we want to keep, +the reset is disallowed. That's why it is disallowed if there are both +changes between the working tree and HEAD, and between HEAD and the +target. To be safe, it is also disallowed when there are unmerged +entries. + +The following tables show what happens when there are unmerged +entries: + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A B --soft (disallowed) + --mixed X B B + --hard B B B + --merge B B B + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A A --soft (disallowed) + --mixed X A A + --hard A A A + --merge A A A + --keep (disallowed) + +X means any state and U means an unmerged index. + + Author ------ Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 0727f431c6..be4c053360 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -184,10 +184,13 @@ scripts the same facilities C builtins have. It works as an option normalizer (e.g. splits single switches aggregate values), a bit like `getopt(1)` does. It takes on the standard input the specification of the options to parse and -understand, and echoes on the standard output a line suitable for `sh(1)` `eval` +understand, and echoes on the standard output a string suitable for `sh(1)` `eval` to replace the arguments with normalized ones. In case of error, it outputs usage on the standard error stream, and exits with code 129. +Note: Make sure you quote the result when passing it to `eval`. See +below for an example. + Input Format ~~~~~~~~~~~~ @@ -244,7 +247,7 @@ bar= some cool option --bar with an argument An option group Header C? option C with an optional argument" -eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` +eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" ------------ SQ-QUOTE diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 3f9d9c6db3..75780d7d63 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -163,9 +163,15 @@ flag, so you can do to get a listing of all tags together with what they dereference. +FILES +----- +`.git/refs/*`, `.git/packed-refs` + SEE ALSO -------- -linkgit:git-ls-remote[1] +linkgit:git-ls-remote[1], +linkgit:git-update-ref[1], +linkgit:gitrepository-layout[5] AUTHORS ------- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 2fd054c104..dae190a5f2 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -55,7 +55,11 @@ specified. --ignore-submodules[=<when>]:: Ignore changes to submodules when looking for changes. <when> can be - either "untracked", "dirty" or "all", which is the default. When + either "none", "untracked", "dirty" or "all", which is the default. + Using "none" will consider the submodule modified when it either contains + untracked or modified files or its HEAD differs from the commit recorded + in the superproject and can be used to override any settings of the + 'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When "untracked" is used submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified content). Using "dirty" ignores all changes to the work tree of submodules, diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b09bd9761f..4b84d08fc8 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -646,6 +646,12 @@ svn.brokenSymlinkWorkaround:: revision fetched. If unset, 'git svn' assumes this option to be "true". +svn.pathnameencoding:: + This instructs git svn to recode pathnames to a given encoding. + It can be used by windows users and by those who work in non-utf8 + locales to avoid corrupted file names with non-ASCII characters. + Valid encodings are the ones supported by Perl's Encode module. + Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by 'git svn'; they *must* be set in the configuration file before any history is imported diff --git a/Documentation/git.txt b/Documentation/git.txt index 27ece58857..531789321c 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,20 +44,24 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.2/git.html[documentation for release 1.7.2] +* link:v1.7.2.2/git.html[documentation for release 1.7.2.2] * release notes for + link:RelNotes-1.7.2.2.txt[1.7.2.2], + link:RelNotes-1.7.2.1.txt[1.7.2.1], link:RelNotes-1.7.2.txt[1.7.2]. -* link:v1.7.1.1/git.html[documentation for release 1.7.1.1] +* link:v1.7.1.2/git.html[documentation for release 1.7.1.2] * release notes for + link:RelNotes-1.7.1.2.txt[1.7.1.2], link:RelNotes-1.7.1.1.txt[1.7.1.1], link:RelNotes-1.7.1.txt[1.7.1]. -* link:v1.7.0.6/git.html[documentation for release 1.7.0.6] +* link:v1.7.0.7/git.html[documentation for release 1.7.0.7] * release notes for + link:RelNotes-1.7.0.7.txt[1.7.0.7], link:RelNotes-1.7.0.6.txt[1.7.0.6], link:RelNotes-1.7.0.5.txt[1.7.0.5], link:RelNotes-1.7.0.4.txt[1.7.0.4], @@ -724,6 +728,13 @@ The documentation for git suite was started by David Greaves <david@dgreaves.com>, and later enhanced greatly by the contributors on the git-list <git@vger.kernel.org>. +Reporting Bugs +-------------- + +Report bugs to the Git mailing list <git@vger.kernel.org> where the +development and maintenance is primarily done. You do not have to be +subscribed to the list to send a message there. + SEE ALSO -------- linkgit:gittutorial[7], linkgit:gittutorial-2[7], diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 564586b943..2e2370ccdb 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -441,6 +441,8 @@ patterns are available: - `cpp` suitable for source code in the C and C++ languages. +- `csharp` suitable for source code in the C# language. + - `html` suitable for HTML/XHTML documents. - `java` suitable for source code in the Java language. diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 72a13d18e0..8ae107da25 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -44,6 +44,21 @@ submodule.<name>.update:: This config option is overridden if 'git submodule update' is given the '--merge' or '--rebase' options. +submodule.<name>.ignore:: + Defines under what circumstances "git status" and the diff family show + a submodule as modified. When set to "all", it will never be considered + modified, "dirty" will ignore all changes to the submodules work tree and + takes only differences between the HEAD of the submodule and the commit + recorded in the superproject into account. "untracked" will additionally + let submodules with modified tracked files in their work tree show up. + Using "none" (the default when this option is not set) also shows + submodules that have untracked files in their work tree as changed. + If this option is also present in the submodules entry in .git/config of + the superproject, the setting there will override the one found in + .gitmodules. + Both settings can be overriden on the command line by using the + "--ignore-submodule" option. + EXAMPLES -------- diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 3cd32d6803..eb3d040783 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -16,7 +16,7 @@ You may find these things in your git repository (`.git` directory for a repository associated with your working tree, or `<project>.git` directory for a public 'bare' repository. It is also possible to have a working tree where `.git` is a plain -ascii file containing `gitdir: <path>`, i.e. the path to the +ASCII file containing `gitdir: <path>`, i.e. the path to the real git repository). objects:: diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index d78e121c76..9b6f3899ec 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -1,5 +1,5 @@ --pretty[='<format>']:: ---format[='<format>']:: +--format='<format>':: Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', diff --git a/Documentation/technical/api-tree-walking.txt b/Documentation/technical/api-tree-walking.txt index 55b728632c..14af37c3f1 100644 --- a/Documentation/technical/api-tree-walking.txt +++ b/Documentation/technical/api-tree-walking.txt @@ -42,6 +42,8 @@ information. * `data` can be anything the `fn` callback would want to use. +* `show_all_errors` tells whether to stop at the first error or not. + Initializing ------------ diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 1dcd1e7f1e..289019478d 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -25,7 +25,7 @@ The ssh and git protocols additionally support ~username expansion: - git://host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/ - {startsb}user@{endsb}host.xz:/~{startsb}user{endsb}/path/to/repo.git/ -For local respositories, also supported by git natively, the following +For local repositories, also supported by git natively, the following syntaxes may be used: - /path/to/repo.git/ diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index e88f50cafb..f6d301a10f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.2 +DEF_VER=v1.7.2.GIT LF=' ' @@ -157,3 +157,36 @@ Issues of note: It has been reported that docbook-xsl version 1.72 and 1.73 are buggy; 1.72 misformats manual pages for callouts, and 1.73 needs the patch in contrib/patches/docbook-xsl-manpages-charmap.patch + + Users attempting to build the documentation on Cygwin may need to ensure + that the /etc/xml/catalog file looks something like this: + + <?xml version="1.0"?> + <!DOCTYPE catalog PUBLIC + "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" + "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd" + > + <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"> + <rewriteURI + uriStartString = "http://docbook.sourceforge.net/release/xsl/current" + rewritePrefix = "/usr/share/sgml/docbook/xsl-stylesheets" + /> + <rewriteURI + uriStartString="http://www.oasis-open.org/docbook/xml/4.5" + rewritePrefix="/usr/share/sgml/docbook/xml-dtd-4.5" + /> + </catalog> + + This can be achieved with the following two xmlcatalog commands: + + xmlcatalog --noout \ + --add rewriteURI \ + http://docbook.sourceforge.net/release/xsl/current \ + /usr/share/sgml/docbook/xsl-stylesheets \ + /etc/xml/catalog + + xmlcatalog --noout \ + --add rewriteURI \ + http://www.oasis-open.org/docbook/xml/4.5/xsl/current \ + /usr/share/sgml/docbook/xml-dtd-4.5 \ + /etc/xml/catalog @@ -1854,8 +1854,9 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h builtin/pack-objects.o: thread-utils.h +connect.o transport.o http-backend.o: url.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h -http.o http-walker.o http-push.o remote-curl.o: http.h +http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h xdiff-interface.o $(XDIFF_OBJS): \ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ @@ -2075,10 +2076,19 @@ endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { test "$$bindir/" = "$$execdir/" || \ - { $(RM) "$$execdir/git$X" && \ + for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ + $(RM) "$$execdir/$$p" && \ test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ - ln "$$bindir/git$X" "$$execdir/git$X" 2>/dev/null || \ - cp "$$bindir/git$X" "$$execdir/git$X"; } ; } && \ + ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ + cp "$$bindir/$$p" "$$execdir/$$p" || exit; \ + done; \ + } && \ + for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \ + $(RM) "$$bindir/$$p" && \ + ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ + cp "$$bindir/git$X" "$$bindir/$$p" || exit; \ + done && \ for p in $(BUILT_INS); do \ $(RM) "$$execdir/$$p" && \ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ @@ -2254,6 +2264,7 @@ check-docs:: documented,gitglossary | \ documented,githooks | \ documented,gitrepository-layout | \ + documented,gitrevisions | \ documented,gittutorial | \ documented,gittutorial-2 | \ documented,git-bisect-lk2009 | \ @@ -1 +1 @@ -Documentation/RelNotes-1.7.2.txt
\ No newline at end of file +Documentation/RelNotes-1.7.2.2.txt
\ No newline at end of file @@ -33,6 +33,7 @@ static void format_subst(const struct commit *commit, struct strbuf fmt = STRBUF_INIT; struct pretty_print_context ctx = {0}; ctx.date_mode = DATE_NORMAL; + ctx.abbrev = DEFAULT_ABBREV; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -7,9 +7,9 @@ #define say1(a,b) fprintf(stderr, a, b) #define say2(a,b,c) fprintf(stderr, a, b, c) #else -#define say(a) do {} while(0) -#define say1(a,b) do {} while(0) -#define say2(a,b,c) do {} while(0) +#define say(a) do { /* nothing */ } while (0) +#define say1(a,b) do { /* nothing */ } while (0) +#define say2(a,b,c) do { /* nothing */ } while (0) #endif static const char en85[] = { @@ -141,7 +141,8 @@ static void show_list(const char *debug, int counted, int nr, enum object_type type; unsigned long size; char *buf = read_sha1_file(commit->object.sha1, &type, &size); - char *ep, *sp; + const char *subject_start; + int subject_len; fprintf(stderr, "%c%c%c ", (flags & TREESAME) ? ' ' : 'T', @@ -156,13 +157,9 @@ static void show_list(const char *debug, int counted, int nr, fprintf(stderr, " %.*s", 8, sha1_to_hex(pp->item->object.sha1)); - sp = strstr(buf, "\n\n"); - if (sp) { - sp += 2; - for (ep = sp; *ep && *ep != '\n'; ep++) - ; - fprintf(stderr, " %.*s", (int)(ep - sp), sp); - } + subject_len = find_commit_subject(buf, &subject_start); + if (subject_len) + fprintf(stderr, " %.*s", subject_len, subject_start); fprintf(stderr, "\n"); } } @@ -159,7 +159,7 @@ void create_branch(const char *head, dont_change_ref = 1; else if (!force) die("A branch named '%s' already exists.", name); - else if (!is_bare_repository() && !strcmp(head, name)) + else if (!is_bare_repository() && head && !strcmp(head, name)) die("Cannot force update the current branch."); forcing = 1; } diff --git a/builtin/apply.c b/builtin/apply.c index 12ef9ea8af..f38c1f7b88 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -2979,8 +2979,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) else if (get_sha1(patch->old_sha1_prefix, sha1)) /* git diff has no index line for mode/type changes */ if (!patch->lines_added && !patch->lines_deleted) { - if (get_current_sha1(patch->new_name, sha1) || - get_current_sha1(patch->old_name, sha1)) + if (get_current_sha1(patch->old_name, sha1)) die("mode change for %s, which is not " "in current HEAD", name); sha1_ptr = sha1; diff --git a/builtin/blame.c b/builtin/blame.c index 01e62fdeb0..437b1a433a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1407,7 +1407,8 @@ static void get_commit_info(struct commit *commit, int detailed) { int len; - char *tmp, *endp, *reencoded, *message; + const char *subject; + char *reencoded, *message; static char author_name[1024]; static char author_mail[1024]; static char committer_name[1024]; @@ -1449,22 +1450,13 @@ static void get_commit_info(struct commit *commit, &ret->committer_time, &ret->committer_tz); ret->summary = summary_buf; - tmp = strstr(message, "\n\n"); - if (!tmp) { - error_out: + 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)); - free(reencoded); - return; } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - endp = tmp + strlen(tmp); - len = endp - tmp; - if (len >= sizeof(summary_buf) || len == 0) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; free(reencoded); } diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index b106c65d80..ae3f28115a 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -33,28 +33,38 @@ static void collapse_slashes(char *dst, const char *src) *dst = '\0'; } +static int check_ref_format_branch(const char *arg) +{ + struct strbuf sb = STRBUF_INIT; + int nongit; + + setup_git_directory_gently(&nongit); + if (strbuf_check_branch_ref(&sb, arg)) + die("'%s' is not a valid branch name", arg); + printf("%s\n", sb.buf + 11); + return 0; +} + +static int check_ref_format_print(const char *arg) +{ + char *refname = xmalloc(strlen(arg) + 1); + + if (check_ref_format(arg)) + return 1; + collapse_slashes(refname, arg); + printf("%s\n", refname); + return 0; +} + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); - if (argc == 3 && !strcmp(argv[1], "--branch")) { - struct strbuf sb = STRBUF_INIT; - - if (strbuf_check_branch_ref(&sb, argv[2])) - die("'%s' is not a valid branch name", argv[2]); - printf("%s\n", sb.buf + 11); - exit(0); - } - if (argc == 3 && !strcmp(argv[1], "--print")) { - char *refname = xmalloc(strlen(argv[2]) + 1); - - if (check_ref_format(argv[2])) - exit(1); - collapse_slashes(refname, argv[2]); - printf("%s\n", refname); - exit(0); - } + if (argc == 3 && !strcmp(argv[1], "--branch")) + return check_ref_format_branch(argv[2]); + if (argc == 3 && !strcmp(argv[1], "--print")) + return check_ref_format_print(argv[2]); if (argc != 2) usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); diff --git a/builtin/checkout.c b/builtin/checkout.c index 1994be92c6..7250e5c23c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -32,7 +32,11 @@ struct checkout_opts { int writeout_stage; int writeout_error; + /* not set by parse_options */ + int branch_exists; + const char *new_branch; + const char *new_branch_force; const char *new_orphan_branch; int new_branch_log; enum branch_track track; @@ -279,7 +283,6 @@ static void show_local_changes(struct object *head) struct rev_info rev; /* I think we want full paths, even if we're in a subdirectory. */ init_revisions(&rev, NULL); - rev.abbrev = 0; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; if (diff_setup_done(&rev.diffopt) < 0) die("diff_setup_done failed"); @@ -373,7 +376,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.src_index = &the_index; topts.dst_index = &the_index; - topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; + set_porcelain_error_msgs(topts.msgs, "checkout"); refresh_cache(REFRESH_QUIET); @@ -392,6 +395,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; topts.dir->exclude_per_dir = ".gitignore"; + topts.show_all_errors = 1; tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : (unsigned char *)EMPTY_TREE_SHA1_BIN); @@ -511,7 +515,8 @@ static void update_refs_for_switch(struct checkout_opts *opts, } } else - create_branch(old->name, opts->new_branch, new->name, 0, + create_branch(old->name, opts->new_branch, new->name, + opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->track); new->name = opts->new_branch; setup_branch_path(new); @@ -529,9 +534,12 @@ static void update_refs_for_switch(struct checkout_opts *opts, if (old->path && !strcmp(new->path, old->path)) fprintf(stderr, "Already on '%s'\n", new->name); - else + else if (opts->new_branch) fprintf(stderr, "Switched to%s branch '%s'\n", - opts->new_branch ? " a new" : "", + opts->branch_exists ? " and reset" : " a new", + new->name); + else + fprintf(stderr, "Switched to branch '%s'\n", new->name); } if (old->path && old->name) { @@ -657,7 +665,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) int dwim_new_local_branch = 1; struct option options[] = { OPT__QUIET(&opts.quiet), - OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), + OPT_STRING('b', NULL, &opts.new_branch, "branch", + "create and checkout a new branch"), + OPT_STRING('B', NULL, &opts.new_branch_force, "branch", + "create/reset and checkout a branch"), OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), OPT_SET_INT('t', "track", &opts.track, "track", BRANCH_TRACK_EXPLICIT), @@ -688,6 +699,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + /* we can assume from now on new_branch = !new_branch_force */ + if (opts.new_branch && opts.new_branch_force) + die("-B cannot be used with -b"); + + /* copy -B over to -b, so that we can just check the latter */ + if (opts.new_branch_force) + opts.new_branch = opts.new_branch_force; + if (patch_mode && (opts.track > 0 || opts.new_branch || opts.new_branch_log || opts.merge || opts.force)) die ("--patch is incompatible with all other options"); @@ -709,7 +728,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.new_orphan_branch) { if (opts.new_branch) - die("--orphan and -b are mutually exclusive"); + die("--orphan and -b|-B are mutually exclusive"); if (opts.track > 0) die("--orphan cannot be used with -t"); opts.new_branch = opts.new_orphan_branch; @@ -858,8 +877,12 @@ no_reference: if (strbuf_check_branch_ref(&buf, opts.new_branch)) die("git checkout: we do not like '%s' as a branch name.", opts.new_branch); - if (!get_sha1(buf.buf, rev)) - die("git checkout: branch %s already exists", opts.new_branch); + if (!get_sha1(buf.buf, rev)) { + opts.branch_exists = 1; + if (!opts.new_branch_force) + die("git checkout: branch %s already exists", + opts.new_branch); + } strbuf_release(&buf); } diff --git a/builtin/clean.c b/builtin/clean.c index fac64e6cd3..b508d2cab4 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,12 +10,13 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" +#include "string-list.h" #include "quote.h" static int force = -1; /* unset */ static const char *const builtin_clean_usage[] = { - "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...", + "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...", NULL }; @@ -26,6 +27,13 @@ static int git_clean_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int exclude_cb(const struct option *opt, const char *arg, int unset) +{ + struct string_list *exclude_list = opt->value; + string_list_append(exclude_list, arg); + return 0; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i; @@ -36,6 +44,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct dir_struct dir; static const char **pathspec; struct strbuf buf = STRBUF_INIT; + struct string_list exclude_list = { NULL, 0, 0, 0 }; const char *qname; char *seen = NULL; struct option options[] = { @@ -44,6 +53,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('f', "force", &force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), + { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", + "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb }, OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), OPT_BOOLEAN('X', NULL, &ignored_only, "remove only ignored files"), @@ -81,6 +92,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!ignored) setup_standard_excludes(&dir); + for (i = 0; i < exclude_list.nr; i++) + add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list); + pathspec = get_pathspec(prefix, argv); fill_directory(&dir, pathspec); @@ -167,5 +181,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) free(seen); strbuf_release(&directory); + string_list_clear(&exclude_list, 0); return (errors != 0); } diff --git a/builtin/commit.c b/builtin/commit.c index a78dbd83bf..66fdd22024 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -25,6 +25,7 @@ #include "rerere.h" #include "unpack-trees.h" #include "quote.h" +#include "submodule.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] <filepattern>...", @@ -147,7 +148,7 @@ static struct option builtin_commit_options[] = { "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */ { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, @@ -1073,6 +1074,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) status_format = STATUS_FORMAT_PORCELAIN; wt_status_prepare(&s); + gitmodules_config(); git_config(git_status_config, &s); in_merge = file_exists(git_path("MERGE_HEAD")); argc = parse_options(argc, argv, prefix, @@ -1163,7 +1165,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1) init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.abbrev = 0; rev.diff = 1; rev.diffopt.output_format = DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; diff --git a/builtin/config.c b/builtin/config.c index f3d1660d02..79fee767b8 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -20,7 +20,7 @@ static char delim = '='; static char key_delim = ' '; static char term = '\n'; -static int use_global_config, use_system_config; +static int use_global_config, use_system_config, use_local_config; static const char *given_config_file; static int actions, types; static const char *get_color_slot, *get_colorbool_slot; @@ -51,6 +51,7 @@ static struct option builtin_config_options[] = { OPT_GROUP("Config file location"), OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), + OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"), OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), OPT_GROUP("Action"), OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), @@ -342,7 +343,7 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (use_global_config + use_system_config + !!given_config_file > 1) { + if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { error("only one config file at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } @@ -358,6 +359,8 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) } else if (use_system_config) config_exclusive_filename = git_etc_gitconfig(); + else if (use_local_config) + config_exclusive_filename = git_pathdup("config"); else if (given_config_file) { if (!is_absolute_path(given_config_file) && prefix) config_exclusive_filename = prefix_filename(prefix, diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 5b64011de8..951c7c8994 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -8,6 +8,7 @@ #include "commit.h" #include "revision.h" #include "builtin.h" +#include "submodule.h" static const char diff_files_usage[] = "git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" @@ -20,6 +21,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) unsigned options = 0; init_revisions(&rev, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 04837494fe..2eb32bd9da 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -3,6 +3,7 @@ #include "commit.h" #include "revision.h" #include "builtin.h" +#include "submodule.h" static const char diff_cache_usage[] = "git diff-index [-m] [--cached] " @@ -17,6 +18,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) int result; init_revisions(&rev, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 3c78bda566..0d2a3e9fa2 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -3,6 +3,7 @@ #include "commit.h" #include "log-tree.h" #include "builtin.h" +#include "submodule.h" static struct rev_info log_tree_opt; @@ -112,6 +113,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int read_stdin = 0; init_revisions(opt, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ opt->abbrev = 0; opt->diff = 1; diff --git a/builtin/diff.c b/builtin/diff.c index 89ae89cde1..a43d326363 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -13,6 +13,7 @@ #include "revision.h" #include "log-tree.h" #include "builtin.h" +#include "submodule.h" struct blobinfo { unsigned char sha1[20]; @@ -279,6 +280,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) */ prefix = setup_git_directory_gently(&nongit); + gitmodules_config(); git_config(git_diff_ui_config, NULL); if (diff_use_color_default == -1) diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9fe25ff0b3..834ec8b464 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -566,7 +566,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct object_array commits = { 0, 0, NULL }; - struct string_list extra_refs = { NULL, 0, 0, 0 }; + struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; struct option options[] = { diff --git a/builtin/fetch.c b/builtin/fetch.c index 6eb1dfea09..ea14d5dc6b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -544,40 +544,14 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } -struct tag_data { - struct ref **head; - struct ref ***tail; -}; - -static int add_to_tail(struct string_list_item *item, void *cb_data) -{ - struct tag_data *data = (struct tag_data *)cb_data; - struct ref *rm = NULL; - - /* We have already decided to ignore this item */ - if (!item->util) - return 0; - - rm = alloc_ref(item->string); - rm->peer_ref = alloc_ref(item->string); - hashcpy(rm->old_sha1, item->util); - - **data->tail = rm; - *data->tail = &rm->next; - - return 0; -} - static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list remote_refs = { NULL, 0, 0, 0 }; - struct tag_data data; + struct string_list existing_refs = STRING_LIST_INIT_NODUP; + struct string_list remote_refs = STRING_LIST_INIT_NODUP; const struct ref *ref; struct string_list_item *item = NULL; - data.head = head; data.tail = tail; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { @@ -631,10 +605,20 @@ static void find_non_local_tags(struct transport *transport, item->util = NULL; /* - * For all the tags in the remote_refs string list, call - * add_to_tail to add them to the list of refs to be fetched + * For all the tags in the remote_refs string list, + * add them to the list of refs to be fetched */ - for_each_string_list(&remote_refs, add_to_tail, &data); + for_each_string_list_item(item, &remote_refs) { + /* Unless we have already decided to ignore this item... */ + if (item->util) + { + struct ref *rm = alloc_ref(item->string); + rm->peer_ref = alloc_ref(item->string); + hashcpy(rm->old_sha1, item->util); + **tail = rm; + *tail = &rm->next; + } + } string_list_clear(&remote_refs, 0); } @@ -667,7 +651,7 @@ static int truncate_fetch_head(void) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { - struct string_list existing_refs = { NULL, 0, 0, 0 }; + struct string_list existing_refs = STRING_LIST_INIT_NODUP; struct string_list_item *peer_item = NULL; struct ref *ref_map; struct ref *rm; @@ -845,7 +829,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) int exit_code; if (!remote) - die("Where do you want to fetch from today?"); + die("No remote repository specified. Please, specify either a URL or a\n" + "remote name from which new revisions should be fetched."); transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); @@ -890,7 +875,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) int cmd_fetch(int argc, const char **argv, const char *prefix) { int i; - struct string_list list = { NULL, 0, 0, 0 }; + struct string_list list = STRING_LIST_INIT_NODUP; struct remote *remote; int result = 0; diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index bc3c5e6d3e..a76cd4e9d1 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -38,8 +38,8 @@ void init_src_data(struct src_data *data) data->generic.strdup_strings = 1; } -static struct string_list srcs = { NULL, 0, 0, 1 }; -static struct string_list origins = { NULL, 0, 0, 1 }; +static struct string_list srcs = STRING_LIST_INIT_DUP; +static struct string_list origins = STRING_LIST_INIT_DUP; static int handle_line(char *line) { @@ -146,7 +146,7 @@ static void shortlog(const char *name, unsigned char *sha1, int i, count = 0; struct commit *commit; struct object *branch; - struct string_list subjects = { NULL, 0, 0, 1 }; + struct string_list subjects = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; diff --git a/builtin/grep.c b/builtin/grep.c index 597f76bc42..cf6c29fa42 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -836,7 +836,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; - struct string_list path_list = { NULL, 0, 0, 0 }; + struct string_list path_list = STRING_LIST_INIT_NODUP; int i; int dummy; int nongit = 0, use_index = 1; diff --git a/builtin/help.c b/builtin/help.c index a9836b00ae..61ff79839b 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -120,7 +120,7 @@ static void exec_woman_emacs(const char *path, const char *page) if (!path) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); + execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -148,7 +148,7 @@ static void exec_man_konqueror(const char *path, const char *page) } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); + execlp(path, filename, "newTab", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -157,7 +157,7 @@ static void exec_man_man(const char *path, const char *page) { if (!path) path = "man"; - execlp(path, "man", page, NULL); + execlp(path, "man", page, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } @@ -165,7 +165,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); warning("failed to exec '%s': %s", cmd, strerror(errno)); } @@ -372,7 +372,7 @@ static void show_info_page(const char *git_cmd) { const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); - execlp("info", "info", "gitman", page, NULL); + execlp("info", "info", "gitman", page, (char *)NULL); die("no info viewer handled the request"); } @@ -398,7 +398,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) #ifndef open_html static void open_html(const char *path) { - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); + execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } #endif diff --git a/builtin/index-pack.c b/builtin/index-pack.c index a89ae831dd..fad76bf7a8 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -884,6 +884,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); + read_replace_refs = 0; + /* * We wish to read the repository's config file if any, and * for that it is necessary to call setup_git_directory_gently(). diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 1b9b8a8b4a..bb4f612b3d 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -25,6 +25,7 @@ static int show_modified; static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; +static int debug_mode; static const char *prefix; static int max_prefix_len; @@ -162,35 +163,41 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) ce_stage(ce)); } write_name(ce->name, ce_namelen(ce)); -} - -static int show_one_ru(struct string_list_item *item, void *cbdata) -{ - const char *path = item->string; - struct resolve_undo_info *ui = item->util; - int i, len; - - len = strlen(path); - if (len < max_prefix_len) - return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) - return 0; /* uninterested */ - for (i = 0; i < 3; i++) { - if (!ui->mode[i]) - continue; - printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - find_unique_abbrev(ui->sha1[i], abbrev), - i + 1); - write_name(path, len); + if (debug_mode) { + printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec); + printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec); + printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino); + printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid); + printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags); } - return 0; } static void show_ru_info(void) { + struct string_list_item *item; + if (!the_index.resolve_undo) return; - for_each_string_list(the_index.resolve_undo, show_one_ru, NULL); + + for_each_string_list_item(item, the_index.resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ui = item->util; + int i, len; + + len = strlen(path); + if (len < max_prefix_len) + continue; /* outside of the prefix */ + if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) + continue; /* uninterested */ + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], + find_unique_abbrev(ui->sha1[i], abbrev), + i + 1); + write_name(path, len); + } + } } static void show_files(struct dir_struct *dir) @@ -519,6 +526,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_STRING(0, "with-tree", &with_tree, "tree-ish", "pretend that paths removed since <tree-ish> are still present"), OPT__ABBREV(&abbrev), + OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"), OPT_END() }; diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index e4560da191..99654d0222 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -137,7 +137,7 @@ static int split_maildir(const char *maildir, const char *dir, char name[PATH_MAX]; int ret = -1; int i; - struct string_list list = {NULL, 0, 0, 1}; + struct string_list list = STRING_LIST_INIT_DUP; if (populate_maildir_list(&list, maildir) < 0) goto out; diff --git a/builtin/merge.c b/builtin/merge.c index 37ce4f589f..1bcf7fd9d5 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -704,7 +704,8 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote opts.verbose_update = 1; opts.merge = 1; opts.fn = twoway_merge; - opts.msgs = get_porcelain_error_msgs(); + opts.show_all_errors = 1; + set_porcelain_error_msgs(opts.msgs, "merge"); trees[nr_trees] = parse_tree_indirect(head); if (!trees[nr_trees++]) diff --git a/builtin/mv.c b/builtin/mv.c index 38574b89f7..cdbb09473c 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -63,7 +63,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char **source, **destination, **dest_path; enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; struct stat st; - struct string_list src_for_dst = {NULL, 0, 0, 0}; + struct string_list src_for_dst = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); diff --git a/builtin/notes.c b/builtin/notes.c index 190005f3cd..fbc347c9f0 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -798,8 +798,9 @@ static int prune(int argc, const char **argv, const char *prefix) struct notes_tree *t; int show_only = 0, verbose = 0; struct option options[] = { - OPT_BOOLEAN('n', NULL, &show_only, "do not remove, show only"), - OPT_BOOLEAN('v', NULL, &verbose, "report pruned notes"), + OPT_BOOLEAN('n', "dry-run", &show_only, + "do not remove, show only"), + OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"), OPT_END() }; diff --git a/builtin/prune.c b/builtin/prune.c index 81f915ec31..99218ba49e 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -125,10 +125,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; const struct option options[] = { - OPT_BOOLEAN('n', NULL, &show_only, + OPT_BOOLEAN('n', "dry-run", &show_only, "do not remove, show only"), - OPT_BOOLEAN('v', NULL, &verbose, - "report pruned objects"), + OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() diff --git a/builtin/push.c b/builtin/push.c index f4358b9d23..e655eb7695 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -22,13 +22,13 @@ static int progress; static const char **refspec; static int refspec_nr; +static int refspec_alloc; static void add_refspec(const char *ref) { - int nr = refspec_nr + 1; - refspec = xrealloc(refspec, nr * sizeof(char *)); - refspec[nr-1] = ref; - refspec_nr = nr; + refspec_nr++; + ALLOC_GROW(refspec, refspec_nr, refspec_alloc); + refspec[refspec_nr-1] = ref; } static void set_refspecs(const char **refs, int nr) @@ -130,8 +130,8 @@ static int push_with_options(struct transport *transport, int flags) if (nonfastforward && advice_push_nonfastforward) { fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'Note about\n" - "fast-forwards' section of 'git push --help' for details.\n"); + "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" + "'Note about fast-forwards' section of 'git push --help' for details.\n"); } return 1; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d634b5a3d5..760817dbd7 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -530,7 +530,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) static void check_aliased_updates(struct command *commands) { struct command *cmd; - struct string_list ref_list = { NULL, 0, 0, 0 }; + struct string_list ref_list = STRING_LIST_INIT_NODUP; for (cmd = commands; cmd; cmd = cmd->next) { struct string_list_item *item = diff --git a/builtin/remote.c b/builtin/remote.c index 6699bc5712..48e0a6bf26 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -134,7 +134,7 @@ static int add_branch(const char *key, const char *branchname, static int add(int argc, const char **argv) { int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT; - struct string_list track = { NULL, 0, 0 }; + struct string_list track = STRING_LIST_INIT_NODUP; const char *master = NULL; struct remote *remote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; @@ -596,7 +596,7 @@ static int mv(int argc, const char **argv) }; struct remote *oldremote, *newremote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT; - struct string_list remote_branches = { NULL, 0, 0, 0 }; + struct string_list remote_branches = STRING_LIST_INIT_NODUP; struct rename_info rename; int i; @@ -734,8 +734,8 @@ static int rm(int argc, const char **argv) struct remote *remote; struct strbuf buf = STRBUF_INIT; struct known_remotes known_remotes = { NULL, NULL }; - struct string_list branches = { NULL, 0, 0, 1 }; - struct string_list skipped = { NULL, 0, 0, 1 }; + struct string_list branches = STRING_LIST_INIT_DUP; + struct string_list skipped = STRING_LIST_INIT_DUP; struct branches_for_remote cb_data; int i, result; @@ -1044,7 +1044,7 @@ static int show(int argc, const char **argv) OPT_END() }; struct ref_states states; - struct string_list info_list = { NULL, 0, 0, 0 }; + struct string_list info_list = STRING_LIST_INIT_NODUP; struct show_info info; argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, @@ -1483,7 +1483,7 @@ static int get_one_entry(struct remote *remote, void *priv) static int show_all(void) { - struct string_list list = { NULL, 0, 0 }; + struct string_list list = STRING_LIST_INIT_NODUP; int result; list.strdup_strings = 1; diff --git a/builtin/rerere.c b/builtin/rerere.c index 39ad60169d..9b1e3a7cf5 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -40,7 +40,7 @@ static int git_rerere_gc_config(const char *var, const char *value, void *cb) static void garbage_collect(struct string_list *rr) { - struct string_list to_remove = { NULL, 0, 0, 1 }; + struct string_list to_remove = STRING_LIST_INIT_DUP; DIR *dir; struct dirent *e; int i, cutoff; @@ -102,7 +102,7 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { - struct string_list merge_rr = { NULL, 0, 0, 1 }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; int i, fd, flags = 0; if (2 < argc) { diff --git a/builtin/revert.c b/builtin/revert.c index 8b9d829a73..9215e66504 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -102,9 +102,9 @@ struct commit_message { static int get_message(const char *raw_message, struct commit_message *out) { const char *encoding; - const char *p, *abbrev, *eol; + const char *abbrev, *subject; + int abbrev_len, subject_len; char *q; - int abbrev_len, oneline_len; if (!raw_message) return -1; @@ -125,27 +125,17 @@ static int get_message(const char *raw_message, struct commit_message *out) abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); abbrev_len = strlen(abbrev); - /* Find beginning and end of commit subject. */ - p = out->message; - while (*p && (*p != '\n' || p[1] != '\n')) - p++; - if (*p) { - p += 2; - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - } else - eol = p; - oneline_len = eol - p; + subject_len = find_commit_subject(out->message, &subject); out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + oneline_len + 1); + strlen("... ") + subject_len + 1); q = out->parent_label; q = mempcpy(q, "parent of ", strlen("parent of ")); out->label = q; q = mempcpy(q, abbrev, abbrev_len); q = mempcpy(q, "... ", strlen("... ")); out->subject = q; - q = mempcpy(q, p, oneline_len); + q = mempcpy(q, subject, subject_len); *q = '\0'; return 0; } diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 0b2a9ad1a9..be9b512eeb 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -120,7 +120,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag */ static int exclude_existing(const char *match) { - static struct string_list existing_refs = { NULL, 0, 0, 0 }; + static struct string_list existing_refs = STRING_LIST_INIT_NODUP; char buf[1024]; int matchlen = match ? strlen(match) : 0; diff --git a/cache-tree.c b/cache-tree.c index d91743775d..c60cf9140d 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -328,9 +328,11 @@ static int update_one(struct cache_tree *it, mode = ce->ce_mode; entlen = pathlen - baselen; } - if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) + if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) { + strbuf_release(&buffer); return error("invalid object %06o %s for '%.*s'", mode, sha1_to_hex(sha1), entlen+baselen, path); + } if (ce->ce_flags & CE_REMOVE) continue; /* entry being removed */ @@ -179,8 +179,7 @@ struct cache_entry { #define CE_UNHASHED (0x200000) #define CE_CONFLICTED (0x800000) -/* Only remove in work directory, not index */ -#define CE_WT_REMOVE (0x400000) +#define CE_WT_REMOVE (0x400000) /* remove in work directory */ #define CE_UNPACKED (0x1000000) @@ -449,7 +448,7 @@ extern int init_db(const char *template_dir, unsigned int flags); alloc = alloc_nr(alloc); \ x = xrealloc((x), alloc * sizeof(*(x))); \ } \ - } while(0) + } while (0) /* Initialize and use the cache information */ extern int read_index(struct index_state *); @@ -641,6 +640,9 @@ extern char *git_pathdup(const char *fmt, ...) /* Return a statically allocated filename matching the sha1 signature */ extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern char *git_path_submodule(const char *path, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); + extern char *sha1_file_name(const unsigned char *sha1); extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); @@ -811,6 +813,7 @@ const char *show_date_relative(unsigned long time, int tz, char *timebuf, size_t timebuf_size); int parse_date(const char *date, char *buf, int bufsize); +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset); void datestamp(char *buf, int bufsize); #define approxidate(s) approxidate_careful((s), NULL) unsigned long approxidate_careful(const char *, int *); @@ -315,6 +315,25 @@ int parse_commit(struct commit *item) return ret; } +int find_commit_subject(const char *commit_buffer, const char **subject) +{ + const char *eol; + const char *p = commit_buffer; + + while (*p && (*p != '\n' || p[1] != '\n')) + p++; + if (*p) { + p += 2; + for (eol = p; *eol && *eol != '\n'; eol++) + ; /* do nothing */ + } else + eol = p; + + *subject = p; + + return eol - p; +} + struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p) { struct commit_list *new_list = xmalloc(sizeof(struct commit_list)); @@ -41,6 +41,9 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); int parse_commit(struct commit *item); +/* Find beginning and length of commit subject. */ +int find_commit_subject(const char *commit_buffer, const char **subject); + struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); unsigned commit_list_count(const struct commit_list *l); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh index 5c72f655c7..23ffb028d1 100755 --- a/contrib/examples/git-commit.sh +++ b/contrib/examples/git-commit.sh @@ -631,7 +631,7 @@ then if test -z "$quiet" then commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\ - --summary --root HEAD --` + --abbrev --summary --root HEAD --` echo "Created${initial_commit:+ initial} commit $commit" fi fi diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 30ae63d74d..0085086437 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -55,6 +55,11 @@ # "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo" # Be careful if "..." contains things that will be expanded by shell "eval" # or printf. +# hooks.emailmaxlines +# The maximum number of lines that should be included in the generated +# email body. If not specified, there is no limit. +# Lines beyond the limit are suppressed and counted, and a final +# line is added indicating the number of suppressed lines. # # Notes # ----- @@ -84,6 +89,7 @@ generate_email() oldrev=$(git rev-parse $1) newrev=$(git rev-parse $2) refname="$3" + maxlines=$4 # --- Interpret # 0000->1234 (create) @@ -192,7 +198,12 @@ generate_email() fn_name=atag ;; esac - generate_${change_type}_${fn_name}_email + + if [ -z "$maxlines" ]; then + generate_${change_type}_${fn_name}_email + else + generate_${change_type}_${fn_name}_email | limit_lines $maxlines + fi generate_email_footer } @@ -203,7 +214,7 @@ generate_email_header() # Generate header cat <<-EOF To: $recipients - Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe + Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe X-Git-Refname: $refname X-Git-Reftype: $refname_type X-Git-Oldrev: $oldrev @@ -642,6 +653,24 @@ show_new_revisions() } +limit_lines() +{ + lines=0 + skipped=0 + while IFS="" read -r line; do + lines=$((lines + 1)) + if [ $lines -gt $1 ]; then + skipped=$((skipped + 1)) + else + printf "%s\n" "$line" + fi + done + if [ $skipped -ne 0 ]; then + echo "... $skipped lines suppressed ..." + fi +} + + send_mail() { if [ -n "$envelopesender" ]; then @@ -679,6 +708,7 @@ announcerecipients=$(git config hooks.announcelist) envelopesender=$(git config hooks.envelopesender) emailprefix=$(git config hooks.emailprefix || echo '[SCM] ') custom_showrev=$(git config hooks.showrev) +maxlines=$(git config hooks.emailmaxlines) # --- Main loop # Allow dual mode: run from the command line just like the update hook, or @@ -691,6 +721,6 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then else while read oldrev newrev refname do - generate_email $oldrev $newrev $refname | send_mail + generate_email $oldrev $newrev $refname $maxlines | send_mail done fi diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore index 27a33b669e..02a7791585 100644 --- a/contrib/svn-fe/.gitignore +++ b/contrib/svn-fe/.gitignore @@ -1,3 +1,4 @@ /*.xml /*.1 /*.html +/svn-fe diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile index 4cc8d15827..360d8da417 100644 --- a/contrib/svn-fe/Makefile +++ b/contrib/svn-fe/Makefile @@ -38,7 +38,7 @@ svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB) $(ALL_LDFLAGS) $(LIBS) svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< + $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $< svn-fe.html: svn-fe.txt $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \ diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c index 43c4320cac..e9b9ba4da4 100644 --- a/contrib/svn-fe/svn-fe.c +++ b/contrib/svn-fe/svn-fe.c @@ -4,7 +4,7 @@ */ #include <stdlib.h> -#include "vcs-svn/svndump.h" +#include "svndump.h" int main(int argc, char **argv) { @@ -586,7 +586,7 @@ static int date_string(unsigned long date, int offset, char *buf, int len) /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 (i.e. English) day/month names, and it doesn't work correctly with %z. */ -int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset) +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset) { struct tm tm; int tm_gmt; @@ -642,17 +642,16 @@ int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset) if (!tm_gmt) *timestamp -= *offset * 60; - return 1; /* success */ + return 0; /* success */ } int parse_date(const char *date, char *result, int maxlen) { unsigned long timestamp; int offset; - if (parse_date_toffset(date, ×tamp, &offset) > 0) - return date_string(timestamp, offset, result, maxlen); - else + if (parse_date_basic(date, ×tamp, &offset)) return -1; + return date_string(timestamp, offset, result, maxlen); } enum date_mode parse_date_format(const char *format) @@ -1004,9 +1003,8 @@ unsigned long approxidate_relative(const char *date, const struct timeval *tv) int offset; int errors = 0; - if (parse_date_toffset(date, ×tamp, &offset) > 0) + if (!parse_date_basic(date, ×tamp, &offset)) return timestamp; - return approxidate_str(date, tv, &errors); } @@ -1019,7 +1017,7 @@ unsigned long approxidate_careful(const char *date, int *error_ret) if (!error_ret) error_ret = &dummy; - if (parse_date_toffset(date, ×tamp, &offset) > 0) { + if (!parse_date_basic(date, ×tamp, &offset)) { *error_ret = 0; return timestamp; } diff --git a/diff-lib.c b/diff-lib.c index 8b8978ae6d..392ce2bef0 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -68,11 +68,16 @@ static int match_stat_with_submodule(struct diff_options *diffopt, unsigned ce_option, unsigned *dirty_submodule) { int changed = ce_match_stat(ce, st, ce_option); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) - && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { - *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); + if (S_ISGITLINK(ce->ce_mode)) { + unsigned orig_flags = diffopt->flags; + if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG)) + set_diffopt_flags_from_submodule_config(diffopt, ce->name); + if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)) + changed = 0; + else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) + && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) + *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); + diffopt->flags = orig_flags; } return changed; } diff --git a/diff-no-index.c b/diff-no-index.c index 43aeeba2e0..ce9e783407 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -64,7 +64,8 @@ static int queue_diff(struct diff_options *o, if (S_ISDIR(mode1) || S_ISDIR(mode2)) { char buffer1[PATH_MAX], buffer2[PATH_MAX]; - struct string_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1}; + struct string_list p1 = STRING_LIST_INIT_DUP; + struct string_list p2 = STRING_LIST_INIT_DUP; int len1 = 0, len2 = 0, i1, i2, ret = 0; if (name1 && read_directory(name1, &p1)) @@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; +static struct diff_options default_diff_options; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, @@ -107,6 +108,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) if (!strcmp(var, "diff.wordregex")) return git_config_string(&diff_word_regex_cfg, var, value); + if (!strcmp(var, "diff.ignoresubmodules")) + handle_ignore_submodules_arg(&default_diff_options, value); + return git_diff_basic_config(var, value, cb); } @@ -141,6 +145,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } + if (!prefixcmp(var, "submodule.")) + return parse_submodule_config_option(var, value); + return git_color_default_config(var, value, cb); } @@ -2704,10 +2711,16 @@ static void diff_fill_sha1_info(struct diff_filespec *one) static void strip_prefix(int prefix_length, const char **namep, const char **otherp) { /* Strip the prefix but do not molest /dev/null and absolute paths */ - if (*namep && **namep != '/') + if (*namep && **namep != '/') { *namep += prefix_length; - if (*otherp && **otherp != '/') + if (**namep == '/') + ++*namep; + } + if (*otherp && **otherp != '/') { *otherp += prefix_length; + if (**otherp == '/') + ++*otherp; + } } static void run_diff(struct diff_filepair *p, struct diff_options *o) @@ -2813,8 +2826,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) void diff_setup(struct diff_options *options) { - memset(options, 0, sizeof(*options)); - memset(&diff_queued_diff, 0, sizeof(diff_queued_diff)); + memcpy(options, &default_diff_options, sizeof(*options)); options->file = stdout; @@ -3234,11 +3246,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--no-textconv")) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); - else if (!strcmp(arg, "--ignore-submodules")) + else if (!strcmp(arg, "--ignore-submodules")) { + DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, "all"); - else if (!prefixcmp(arg, "--ignore-submodules=")) + } else if (!prefixcmp(arg, "--ignore-submodules=")) { + DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, arg + 20); - else if (!strcmp(arg, "--submodule")) + } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { if (!strcmp(arg + 12, "log")) @@ -4140,25 +4154,24 @@ void diffcore_fix_diff_index(struct diff_options *options) void diffcore_std(struct diff_options *options) { - /* We never run this function more than one time, because the - * rename/copy detection logic can only run once. - */ - if (diff_queued_diff.run) - return; - if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); - if (options->break_opt != -1) - diffcore_break(options->break_opt); - if (options->detect_rename) - diffcore_rename(options); - if (options->break_opt != -1) - diffcore_merge_broken(); + if (!options->found_follow) { + /* See try_to_follow_renames() in tree-diff.c */ + if (options->break_opt != -1) + diffcore_break(options->break_opt); + if (options->detect_rename) + diffcore_rename(options); + if (options->break_opt != -1) + diffcore_merge_broken(); + } if (options->pickaxe) diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); if (options->orderfile) diffcore_order(options->orderfile); - diff_resolve_rename_copy(); + if (!options->found_follow) + /* See try_to_follow_renames() in tree-diff.c */ + diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) @@ -4166,7 +4179,7 @@ void diffcore_std(struct diff_options *options) else DIFF_OPT_CLR(options, HAS_CHANGES); - diff_queued_diff.run = 1; + options->found_follow = 0; } int diff_result_code(struct diff_options *opt, int status) @@ -4184,6 +4197,24 @@ int diff_result_code(struct diff_options *opt, int status) return result; } +/* + * Shall changes to this submodule be ignored? + * + * Submodule changes can be configured to be ignored separately for each path, + * but that configuration can be overridden from the command line. + */ +static int is_submodule_ignored(const char *path, struct diff_options *options) +{ + int ignored = 0; + unsigned orig_flags = options->flags; + if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG)) + set_diffopt_flags_from_submodule_config(options, path); + if (DIFF_OPT_TST(options, IGNORE_SUBMODULES)) + ignored = 1; + options->flags = orig_flags; + return ignored; +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, @@ -4191,7 +4222,7 @@ void diff_addremove(struct diff_options *options, { struct diff_filespec *one, *two; - if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode)) + if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options)) return; /* This may look odd, but it is a preparation for @@ -4238,8 +4269,8 @@ void diff_change(struct diff_options *options, { struct diff_filespec *one, *two; - if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode) - && S_ISGITLINK(new_mode)) + if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) && + is_submodule_ignored(concatpath, options)) return; if (DIFF_OPT_TST(options, REVERSE_DIFF)) { @@ -77,6 +77,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) +#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) @@ -126,6 +127,9 @@ struct diff_options { /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; + /* to support internal diff recursion by --follow hack*/ + int found_follow; + FILE *file; int close_file; diff --git a/diffcore.h b/diffcore.h index 491bea0b44..8b3241ad13 100644 --- a/diffcore.h +++ b/diffcore.h @@ -18,7 +18,7 @@ #define MAX_SCORE 60000.0 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */ #define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */ -#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */ +#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen (60%) */ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ @@ -91,14 +91,12 @@ struct diff_queue_struct { struct diff_filepair **queue; int alloc; int nr; - int run; }; #define DIFF_QUEUE_CLEAR(q) \ do { \ (q)->queue = NULL; \ (q)->nr = (q)->alloc = 0; \ - (q)->run = 0; \ - } while(0); + } while (0) extern struct diff_queue_struct diff_queued_diff; extern struct diff_filepair *diff_queue(struct diff_queue_struct *, @@ -118,9 +116,9 @@ void diff_debug_filespec(struct diff_filespec *, int, const char *); void diff_debug_filepair(const struct diff_filepair *, int); void diff_debug_queue(const char *, struct diff_queue_struct *); #else -#define diff_debug_filespec(a,b,c) do {} while(0) -#define diff_debug_filepair(a,b) do {} while(0) -#define diff_debug_queue(a,b) do {} while(0) +#define diff_debug_filespec(a,b,c) do { /* nothing */ } while (0) +#define diff_debug_filepair(a,b) do { /* nothing */ } while (0) +#define diff_debug_queue(a,b) do { /* nothing */ } while (0) #endif extern int diffcore_count_changes(struct diff_filespec *src, diff --git a/fast-import.c b/fast-import.c index 1e5d66ed0a..dd51ac48b6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1666,7 +1666,7 @@ static void dump_marks_helper(FILE *f, if (m->shift) { for (k = 0; k < 1024; k++) { if (m->data.sets[k]) - dump_marks_helper(f, (base + k) << m->shift, + dump_marks_helper(f, base + (k << m->shift), m->data.sets[k]); } } else { @@ -2131,6 +2131,7 @@ static void file_change_m(struct branch *b) case S_IFREG | 0644: case S_IFREG | 0755: case S_IFLNK: + case S_IFDIR: case S_IFGITLINK: /* ok */ break; @@ -2176,23 +2177,28 @@ static void file_change_m(struct branch *b) * another repository. */ } else if (inline_data) { + if (S_ISDIR(mode)) + die("Directories cannot be specified 'inline': %s", + command_buf.buf); if (p != uq.buf) { strbuf_addstr(&uq, p); p = uq.buf; } read_next_command(); parse_and_store_blob(&last_blob, sha1, 0); - } else if (oe) { - if (oe->type != OBJ_BLOB) - die("Not a blob (actually a %s): %s", - typename(oe->type), command_buf.buf); } else { - enum object_type type = sha1_object_info(sha1, NULL); + enum object_type expected = S_ISDIR(mode) ? + OBJ_TREE: OBJ_BLOB; + enum object_type type = oe ? oe->type : + sha1_object_info(sha1, NULL); if (type < 0) - die("Blob not found: %s", command_buf.buf); - if (type != OBJ_BLOB) - die("Not a blob (actually a %s): %s", - typename(type), command_buf.buf); + die("%s not found: %s", + S_ISDIR(mode) ? "Tree" : "Blob", + command_buf.buf); + if (type != expected) + die("Not a %s (actually a %s): %s", + typename(expected), typename(type), + command_buf.buf); } tree_content_set(&b->branch_tree, p, sha1, mode, NULL); diff --git a/git-compat-util.h b/git-compat-util.h index 02a73eeb66..fe845ae639 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -388,6 +388,8 @@ extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1); static inline size_t xsize_t(off_t len) { + if (len > (size_t) len) + die("Cannot handle files this big"); return (size_t)len; } diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 7d5451198c..bb104895a9 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err] tk_messageBox \ -icon error \ -type ok \ - -title [mc "git-gui: fatal error"] \ + -title "git-gui: fatal error" \ -message $err exit 1 } @@ -269,6 +269,17 @@ proc is_config_true {name} { } } +proc is_config_false {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return 0 + } elseif {$v eq {false} || $v eq {0} || $v eq {no}} { + return 1 + } else { + return 0 + } +} + proc get_config {name} { global repo_config if {[catch {set v $repo_config($name)}]} { @@ -323,6 +334,8 @@ proc _trace_exec {cmd} { puts stderr $d } +#'" fix poor old emacs font-lock mode + proc _git_cmd {name} { global _git_cmd_path @@ -416,6 +429,9 @@ proc _lappend_nice {cmd_var} { if {![info exists _nice]} { set _nice [_which nice] + if {[catch {exec $_nice git version}]} { + set _nice {} + } } if {$_nice ne {}} { lappend cmd $_nice @@ -634,6 +650,7 @@ proc rmsel_tag {text} { return $text } +wm withdraw . set root_exists 0 bind . <Visibility> { bind . <Visibility> {} @@ -782,6 +799,7 @@ set default_config(user.email) {} set default_config(gui.encoding) [encoding system] set default_config(gui.matchtrackingbranch) false +set default_config(gui.textconv) true set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.fastcopyblame) false @@ -1155,6 +1173,9 @@ apply_config # try to set work tree from environment, falling back to core.worktree if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { set _gitworktree [get_config core.worktree] + if {$_gitworktree eq ""} { + set _gitworktree [file dirname [file normalize $_gitdir]] + } } if {$_prefix ne {}} { if {$_gitworktree eq {}} { @@ -2098,7 +2119,7 @@ proc do_explore {} { # freedesktop.org-conforming system is our best shot set explorer "xdg-open" } - eval exec $explorer $_gitworktree & + eval exec $explorer [list [file nativename $_gitworktree]] & } set is_quitting 0 @@ -2901,6 +2922,7 @@ blame { set current_branch $head } + wm deiconify . switch -- $subcommand { browser { if {$jump_spec ne {}} usage @@ -3405,6 +3427,19 @@ lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] $ctxmsm add separator create_common_diff_popup $ctxmsm +proc has_textconv {path} { + if {[is_config_false gui.textconv]} { + return 0 + } + set filter [gitattr $path diff set] + set textconv [get_config [join [list diff $filter textconv] .]] + if {$filter ne {set} && $textconv ne {}} { + return 1 + } else { + return 0 + } +} + proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { global current_diff_path file_states set ::cursorX $x @@ -3440,7 +3475,8 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { || {__} eq $state || {_O} eq $state || {_T} eq $state - || {T_} eq $state} { + || {T_} eq $state + || [has_textconv $current_diff_path]} { set s disabled } else { set s normal @@ -3460,29 +3496,44 @@ $main_status show [mc "Initializing..."] # -- Load geometry # -catch { -set gm $repo_config(gui.geometry) -wm geometry . [lindex $gm 0] -if {$use_ttk} { - .vpane sashpos 0 [lindex $gm 1] - .vpane.files sashpos 0 [lindex $gm 2] -} else { - .vpane sash place 0 \ - [lindex $gm 1] \ - [lindex [.vpane sash coord 0] 1] - .vpane.files sash place 0 \ - [lindex [.vpane.files sash coord 0] 0] \ - [lindex $gm 2] +proc on_ttk_pane_mapped {w pane pos} { + bind $w <Map> {} + after 0 [list after idle [list $w sashpos $pane $pos]] +} +proc on_tk_pane_mapped {w pane x y} { + bind $w <Map> {} + after 0 [list after idle [list $w sash place $pane $x $y]] +} +proc on_application_mapped {} { + global repo_config use_ttk + bind . <Map> {} + set gm $repo_config(gui.geometry) + if {$use_ttk} { + bind .vpane <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 1]] + bind .vpane.files <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 2]] + } else { + bind .vpane <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex $gm 1] \ + [lindex [.vpane sash coord 0] 1]] + bind .vpane.files <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex [.vpane.files sash coord 0] 0] \ + [lindex $gm 2]] + } + wm geometry . [lindex $gm 0] } -unset gm +if {[info exists repo_config(gui.geometry)]} { + bind . <Map> [list on_application_mapped] + wm geometry . [lindex $repo_config(gui.geometry) 0] } # -- Load window state # -catch { -set gws $repo_config(gui.wmstate) -wm state . $gws -unset gws +if {[info exists repo_config(gui.wmstate)]} { + catch {wm state . $repo_config(gui.wmstate)} } # -- Key Bindings diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 786b50b8c2..2137ec9684 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -449,11 +449,28 @@ method _load {jump} { $status show [mc "Reading %s..." "$commit:[escape_path $path]"] $w_path conf -text [escape_path $path] + + set do_textconv 0 + if {![is_config_false gui.textconv] && [git-version >= 1.7.2]} { + set filter [gitattr $path diff set] + set textconv [get_config [join [list diff $filter textconv] .]] + if {$filter ne {set} && $textconv ne {}} { + set do_textconv 1 + } + } if {$commit eq {}} { - set fd [open $path r] + if {$do_textconv ne 0} { + set fd [open |[list $textconv $path] r] + } else { + set fd [open $path r] + } fconfigure $fd -eofchar {} } else { - set fd [git_read cat-file blob "$commit:$path"] + if {$do_textconv ne 0} { + set fd [git_read cat-file --textconv "$commit:$path"] + } else { + set fd [git_read cat-file blob "$commit:$path"] + } } fconfigure $fd \ -blocking 0 \ diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 64f06748b6..fae119286d 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -100,12 +100,17 @@ constructor pick {} { $opts insert end [mc "Clone Existing Repository"] link_clone $opts insert end "\n" if {$m_repo ne {}} { + if {[tk windowingsystem] eq "win32"} { + set key L + } else { + set key C + } $m_repo add command \ -command [cb _next clone] \ - -accelerator $M1T-C \ + -accelerator $M1T-$key \ -label [mc "Clone..."] - bind $top <$M1B-c> [cb _next clone] - bind $top <$M1B-C> [cb _next clone] + bind $top <$M1B-[string tolower $key]> [cb _next clone] + bind $top <$M1B-[string toupper $key]> [cb _next clone] } $opts tag conf link_open -foreground blue -underline 1 diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index ec8c11eeb7..c628750276 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -55,7 +55,7 @@ proc handle_empty_diff {} { set path $current_diff_path set s $file_states($path) - if {[lindex $s 0] ne {_M}} return + if {[lindex $s 0] ne {_M} || [has_textconv $path]} return # Prevent infinite rescan loops incr diff_empty_count @@ -280,6 +280,9 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd diff-files } } + if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} { + lappend cmd --textconv + } if {[string match {160000 *} [lindex $s 2]] || [string match {160000 *} [lindex $s 3]]} { diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index d4c5e45c8a..3807c8d283 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -148,6 +148,7 @@ proc do_options {} { {b gui.trustmtime {mc "Trust File Modification Timestamps"}} {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}} {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} + {b gui.textconv {mc "Use Textconv For Diffs and Blames"}} {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}} {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}} {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}} diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 79c1888e11..78878ef89d 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -16,7 +16,7 @@ proc do_windows_shortcut {} { [info nameofexecutable] \ [file normalize $::argv0] \ ] \ - [file normalize [$_gitworktree]] + [file normalize $_gitworktree] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } @@ -57,7 +57,7 @@ proc do_cygwin_shortcut {} { $sh -c \ "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \ ] \ - [file normalize [$_gitworktree]] + [file normalize $_gitworktree] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl index 5fe3aad382..95cb44991f 100644 --- a/git-gui/lib/status_bar.tcl +++ b/git-gui/lib/status_bar.tcl @@ -39,6 +39,7 @@ method _oneline_pack {} { } constructor two_line {path} { + global NS set w $path set w_l $w.l set w_c $w.c diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl index d7f93d045d..db91ab84a5 100644 --- a/git-gui/lib/win32.tcl +++ b/git-gui/lib/win32.tcl @@ -18,9 +18,9 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} { eval [list exec wscript.exe \ /E:jscript \ /nologo \ - [file join $oguilib win32_shortcut.js] \ + [file nativename [file join $oguilib win32_shortcut.js]] \ $lnk_path \ - [file join $oguilib git-gui.ico] \ + [file nativename [file join $oguilib git-gui.ico]] \ $lnk_dir \ $lnk_exec] $lnk_args } diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh index 66bbb2f8fa..b1845c5055 100644 --- a/git-gui/windows/git-gui.sh +++ b/git-gui/windows/git-gui.sh @@ -13,10 +13,11 @@ if { $argc >=2 && [lindex $argv 0] == "--working-dir" } { incr argc -2 } -set bindir [file dirname \ +set basedir [file dirname \ [file dirname \ [file dirname [info script]]]] -set bindir [file join $bindir bin] +set bindir [file join $basedir bin] +set bindir "$bindir;[file join $basedir mingw bin]" regsub -all ";" $bindir "\\;" bindir set env(PATH) "$bindir;$env(PATH)" unset bindir diff --git a/git-instaweb.sh b/git-instaweb.sh index 6635fbefdf..e6f6ecda17 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -43,7 +43,8 @@ test -z "$port" && port=1234 resolve_full_httpd () { case "$httpd" in - *apache2*|*lighttpd*) + *apache2*|*lighttpd*|*httpd*) + # yes, *httpd* covers *lighttpd* above, but it is there for clarity # ensure that the apache2/lighttpd command ends with "-f" if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1 then @@ -56,6 +57,13 @@ resolve_full_httpd () { httpd_only="${httpd%% *}" # cut on first space return ;; + *webrick*) + # server is started by running via generated webrick.rb in + # $fqgitdir/gitweb + full_httpd="$fqgitdir/gitweb/webrick.rb" + httpd_only="${httpd%% *}" # cut on first space + return + ;; esac httpd_only="$(echo $httpd | cut -f1 -d' ')" @@ -187,40 +195,53 @@ GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl" export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG webrick_conf () { + # webrick seems to have no way of passing arbitrary environment + # variables to the underlying CGI executable, so we wrap the + # actual gitweb.cgi using a shell script to force it + wrapper="$fqgitdir/gitweb/$httpd/wrapper.sh" + cat > "$wrapper" <<EOF +#!/bin/sh +# we use this shell script wrapper around the real gitweb.cgi since +# there appears to be no other way to pass arbitrary environment variables +# into the CGI process +GIT_EXEC_PATH=$GIT_EXEC_PATH GIT_DIR=$GIT_DIR GITWEB_CONFIG=$GITWEB_CONFIG +export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG +exec $root/gitweb.cgi +EOF + chmod +x "$wrapper" + + # This assumes _ruby_ is in the user's $PATH. that's _one_ + # portable way to run ruby, which could be installed anywhere, really. # generate a standalone server script in $fqgitdir/gitweb. cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF +#!/usr/bin/env ruby require 'webrick' -require 'yaml' -options = YAML::load_file(ARGV[0]) -options[:StartCallback] = proc do - File.open(options[:PidFile],"w") do |f| - f.puts Process.pid - end -end -options[:ServerType] = WEBrick::Daemon +require 'logger' +options = { + :Port => $port, + :DocumentRoot => "$root", + :Logger => Logger.new('$fqgitdir/gitweb/error.log'), + :AccessLog => [ + [ Logger.new('$fqgitdir/gitweb/access.log'), + WEBrick::AccessLog::COMBINED_LOG_FORMAT ] + ], + :DirectoryIndex => ["gitweb.cgi"], + :CGIInterpreter => "$wrapper", + :StartCallback => lambda do + File.open("$fqgitdir/pid", "w") { |f| f.puts Process.pid } + end, + :ServerType => WEBrick::Daemon, +} +options[:BindAddress] = '127.0.0.1' if "$local" == "true" server = WEBrick::HTTPServer.new(options) ['INT', 'TERM'].each do |signal| trap(signal) {server.shutdown} end server.start EOF - # generate a shell script to invoke the above ruby script, - # which assumes _ruby_ is in the user's $PATH. that's _one_ - # portable way to run ruby, which could be installed anywhere, - # really. - cat >"$fqgitdir/gitweb/$httpd" <<EOF -#!/bin/sh -exec ruby "$fqgitdir/gitweb/$httpd.rb" \$* -EOF - chmod +x "$fqgitdir/gitweb/$httpd" - - cat >"$conf" <<EOF -:Port: $port -:DocumentRoot: "$root" -:DirectoryIndex: ["gitweb.cgi"] -:PidFile: "$fqgitdir/pid" -EOF - test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf" + chmod +x "$fqgitdir/gitweb/$httpd.rb" + # configuration is embedded in server script file, webrick.rb + rm -f "$conf" } lighttpd_conf () { @@ -300,7 +321,13 @@ EOF } apache2_conf () { - test -z "$module_path" && module_path=/usr/lib/apache2/modules + if test -z "$module_path" + then + test -d "/usr/lib/httpd/modules" && + module_path="/usr/lib/httpd/modules" + test -d "/usr/lib/apache2/modules" && + module_path="/usr/lib/apache2/modules" + fi bind= test x"$local" = xtrue && bind='127.0.0.1:' echo 'text/css css' > "$fqgitdir/mime.types" @@ -314,8 +341,10 @@ PidFile "$fqgitdir/pid" Listen $bind$port EOF - for mod in mime dir; do - if test -e $module_path/mod_${mod}.so; then + for mod in mime dir env log_config + do + if test -e $module_path/mod_${mod}.so + then echo "LoadModule ${mod}_module " \ "$module_path/mod_${mod}.so" >> "$conf" fi @@ -334,7 +363,7 @@ EOF cat >> "$conf" <<EOF LoadModule perl_module $module_path/mod_perl.so PerlPassEnv GIT_DIR -PerlPassEnv GIT_EXEC_DIR +PerlPassEnv GIT_EXEC_PATH PerlPassEnv GITWEB_CONFIG <Location /gitweb.cgi> SetHandler perl-script @@ -364,6 +393,9 @@ EOF echo "ScriptSock logs/gitweb.sock" >> "$conf" fi cat >> "$conf" <<EOF +PassEnv GIT_DIR +PassEnv GIT_EXEC_PATH +PassEnv GITWEB_CONFIG AddHandler cgi-script .cgi <Location /gitweb.cgi> Options +ExecCGI @@ -560,7 +592,7 @@ case "$httpd" in *lighttpd*) lighttpd_conf ;; -*apache2*) +*apache2*|*httpd*) apache2_conf ;; webrick) diff --git a/git-mergetool.sh b/git-mergetool.sh index b52a7410bc..bd7ab02f11 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -264,17 +264,35 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa last_status=0 rollup_status=0 +rerere=false + +files_to_merge() { + if test "$rerere" = true + then + git rerere status + else + git ls-files -u | sed -e 's/^[^ ]* //' | sort -u + fi +} + if test $# -eq 0 ; then - files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u) + cd_to_toplevel + + if test -e "$GIT_DIR/MERGE_RR" + then + rerere=true + fi + + files=$(files_to_merge) if test -z "$files" ; then echo "No files need merging" exit 0 fi - echo Merging the files: "$files" - git ls-files -u | - sed -e 's/^[^ ]* //' | - sort -u | + printf "Merging:\n" + printf "$files\n" + + files_to_merge | while IFS= read i do if test $last_status -ne 0; then diff --git a/git-pull.sh b/git-pull.sh index a09a44ec4c..8eb74d45de 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -273,6 +273,15 @@ then exit fi +if test true = "$rebase" +then + o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref) + if test "$oldremoteref" = "$o" + then + unset oldremoteref + fi +fi + merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit case "$rebase" in true) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 31e68603f4..b94c2a0386 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -119,7 +119,7 @@ run 'git rebase --continue'" export GIT_CHERRY_PICK_HELP warn () { - echo "$*" >&2 + printf '%s\n' "$*" >&2 } output () { @@ -606,7 +606,7 @@ skip_unnecessary_picks () { fd=1 ;; esac - echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd + printf '%s\n' "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd done <"$TODO" >"$TODO.new" 3>>"$DONE" && mv -f "$TODO".new "$TODO" && case "$(peek_next_command)" in @@ -649,12 +649,12 @@ rearrange_squash () { case " $used" in *" $sha1 "*) continue ;; esac - echo "$pick $sha1 $message" + printf '%s\n' "$pick $sha1 $message" while read -r squash action msg do case "$message" in "$msg"*) - echo "$action $squash $action! $msg" + printf '%s\n' "$action $squash $action! $msg" used="$used$squash " ;; esac @@ -895,7 +895,7 @@ first and then run 'git rebase --continue' again." do if test t != "$PRESERVE_MERGES" then - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) if test -z "$REBASE_ROOT" @@ -914,7 +914,7 @@ first and then run 'git rebase --continue' again." if test f = "$preserve" then touch "$REWRITTEN"/$sha1 - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" fi fi done diff --git a/git-rebase.sh b/git-rebase.sh index ab4afa7dee..7508463b30 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -44,6 +44,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase strategy=recursive +strategy_opts= do_merge= dotest="$GIT_DIR"/rebase-merge prec=4 @@ -112,7 +113,7 @@ call_merge () { then export GIT_MERGE_VERBOSITY=1 fi - git-merge-$strategy "$cmt^" -- "$hd" "$cmt" + eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' rv=$? case "$rv" in 0) @@ -208,6 +209,7 @@ do test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || die "No rebase in progress?" + git update-index --ignore-submodules --refresh && git diff-files --quiet --ignore-submodules || { echo "You must edit all merge conflicts and then" echo "mark them as resolved using git add" @@ -293,6 +295,27 @@ do -M|-m|--m|--me|--mer|--merg|--merge) do_merge=t ;; + -X*|--strategy-option*) + case "$#,$1" in + 1,-X|1,--strategy-option) + usage ;; + *,-X|*,--strategy-option) + newopt="$2" + shift ;; + *,--strategy-option=*) + newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;; + *,-X*) + newopt="$(expr " $1" : ' -X\(.*\)')" ;; + 1,*) + usage ;; + esac + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")" + do_merge=t + if test -n "$strategy" + then + strategy=recursive + fi + ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -345,7 +368,7 @@ do --root) rebase_root=t ;; - -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff) + -f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff) force_rebase=t ;; --rerere-autoupdate|--no-rerere-autoupdate) @@ -543,7 +566,7 @@ fi if test -z "$do_merge" then git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - $root_flag "$revisions" | + --no-renames $root_flag "$revisions" | git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? diff --git a/git-svn.perl b/git-svn.perl index c4163584a9..9b046b693f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -494,6 +494,7 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; + command_noisy(qw/update-index --refresh/); git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) } 'Cannot dcommit with a dirty index. Commit your changes first, ' . "or stash them with `git stash'.\n"; @@ -1819,6 +1820,7 @@ sub read_all_remotes { die("svn-remote.$remote: remote ref '$remote_ref' " . "must start with 'refs/'\n") unless $remote_ref =~ m{^refs/}; + $local_ref = uri_decode($local_ref); $r->{$remote}->{fetch}->{$local_ref} = $remote_ref; $r->{$remote}->{svm} = {} if $use_svm_props; } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) { @@ -1831,6 +1833,7 @@ sub read_all_remotes { die("svn-remote.$remote: remote ref '$remote_ref' ($t) " . "must start with 'refs/'\n") unless $remote_ref =~ m{^refs/}; + $local_ref = uri_decode($local_ref); my $rs = { t => $t, remote => $remote, @@ -2956,18 +2959,29 @@ sub other_gs { my $gs = Git::SVN->find_by_url($new_url, $url, $branch_from); unless ($gs) { my $ref_id = $old_ref_id; - $ref_id =~ s/\@\d+$//; + $ref_id =~ s/\@\d+-*$//; $ref_id .= "\@$r"; # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); - print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1; my ($u, $p, $repo_id) = ($new_url, '', $ref_id); if ($u =~ s#^\Q$url\E(/|$)##) { $p = $u; $u = $url; $repo_id = $self->{repo_id}; } - $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); + while (1) { + # It is possible to tag two different subdirectories at + # the same revision. If the url for an existing ref + # does not match, we must either find a ref with a + # matching url or create a new ref by growing a tail. + $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); + my (undef, $max_commit) = $gs->rev_map_max(1); + last if (!$max_commit); + my ($url) = ::cmt_metadata($max_commit); + last if ($url eq $gs->full_url); + $ref_id .= '-'; + } + print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1; } $gs } @@ -4050,6 +4064,7 @@ sub new { $self->{absent_dir} = {}; $self->{absent_file} = {}; $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); + $self->{pathnameencoding} = Git::config('svn.pathnameencoding'); $self; } @@ -4133,6 +4148,10 @@ sub open_directory { sub git_path { my ($self, $path) = @_; + if (my $enc = $self->{pathnameencoding}) { + require Encode; + Encode::from_to($path, 'UTF-8', $enc); + } if ($self->{path_strip}) { $path =~ s!$self->{path_strip}!! or die "Failed to strip path '$path' ($self->{path_strip})\n"; @@ -4521,6 +4540,10 @@ sub split_path { sub repo_path { my ($self, $path) = @_; + if (my $enc = $self->{pathnameencoding}) { + require Encode; + Encode::from_to($path, $enc, 'UTF-8'); + } $self->{path_prefix}.(defined $path ? $path : ''); } diff --git a/git-web--browse.sh b/git-web--browse.sh index dbded76aaf..3fc4166b25 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -31,7 +31,7 @@ valid_custom_tool() valid_tool() { case "$1" in - firefox | iceweasel | chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start) + firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start) ;; # happy *) valid_custom_tool "$1" || return 1 @@ -103,7 +103,7 @@ fi if test -z "$browser" ; then if test -n "$DISPLAY"; then - browser_candidates="firefox iceweasel chrome chromium konqueror w3m links lynx dillo" + browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo" if test "$KDE_FULL_SESSION" = "true"; then browser_candidates="konqueror $browser_candidates" fi @@ -146,7 +146,7 @@ case "$browser" in test "$vers" -lt 2 && NEWTAB='' "$browser_path" $NEWTAB "$@" & ;; - chrome|chromium) + google-chrome|chrome|chromium) # Actual command for chromium is chromium-browser. # No need to specify newTab. It's default in chromium eval "$browser_path" "$@" & @@ -8,8 +8,8 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" - " [-c name=value\n" - " [--help] COMMAND [ARGS]"; + " [-c name=value] [--help]\n" + " COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cedc357313..898b121b50 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -232,6 +232,29 @@ our %avatar_size = ( # Leave it undefined (or set to 'undef') to turn off load checking. our $maxload = 300; +# configuration for 'highlight' (http://www.andre-simon.de/) +# match by basename +our %highlight_basename = ( + #'Program' => 'py', + #'Library' => 'py', + 'SConstruct' => 'py', # SCons equivalent of Makefile + 'Makefile' => 'make', +); +# match by extension +our %highlight_ext = ( + # main extensions, defining name of syntax; + # see files in /usr/share/highlight/langDefs/ directory + map { $_ => $_ } + qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl), + # alternate extensions, see /etc/highlight/filetypes.conf + 'h' => 'c', + map { $_ => 'cpp' } qw(cxx c++ cc), + map { $_ => 'php' } qw(php3 php4), + map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' + 'mak' => 'make', + map { $_ => 'xml' } qw(xhtml html htm), +); + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -1037,8 +1060,12 @@ sub run_request { reset_timer(); evaluate_uri(); + evaluate_gitweb_config(); check_loadavg(); + # $projectroot and $projects_list might be set in gitweb config file + $projects_list ||= $projectroot; + evaluate_query_params(); evaluate_path_info(); evaluate_and_validate_params(); @@ -1086,12 +1113,8 @@ sub evaluate_argv { sub run { evaluate_argv(); - evaluate_gitweb_config(); evaluate_git_version(); - # $projectroot and $projects_list might be set in gitweb config file - $projects_list ||= $projectroot; - $pre_listen_hook->() if $pre_listen_hook; @@ -1102,7 +1125,7 @@ sub run { run_request(); - $pre_dispatch_hook->() + $post_dispatch_hook->() if $post_dispatch_hook; last REQUEST if ($is_last_request->()); @@ -3316,30 +3339,6 @@ sub blob_contenttype { sub guess_file_syntax { my ($highlight, $mimetype, $file_name) = @_; return undef unless ($highlight && defined $file_name); - - # configuration for 'highlight' (http://www.andre-simon.de/) - # match by basename - my %highlight_basename = ( - #'Program' => 'py', - #'Library' => 'py', - 'SConstruct' => 'py', # SCons equivalent of Makefile - 'Makefile' => 'make', - ); - # match by extension - my %highlight_ext = ( - # main extensions, defining name of syntax; - # see files in /usr/share/highlight/langDefs/ directory - map { $_ => $_ } - qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl), - # alternate extensions, see /etc/highlight/filetypes.conf - 'h' => 'c', - map { $_ => 'cpp' } qw(cxx c++ cc), - map { $_ => 'php' } qw(php3 php4), - map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' - 'mak' => 'make', - map { $_ => 'xml' } qw(xhtml html htm), - ); - my $basename = basename($file_name, '.in'); return $highlight_basename{$basename} if exists $highlight_basename{$basename}; @@ -6522,12 +6521,13 @@ sub git_search { $paging_nav .= " ⋅ next"; } - if ($#commitlist >= 100) { - } - git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash); - git_search_grep_body(\@commitlist, 0, 99, $next_link); + if ($page == 0 && !@commitlist) { + print "<p>No match.</p>\n"; + } else { + git_search_grep_body(\@commitlist, 0, 99, $next_link); + } } if ($searchtype eq 'pickaxe') { @@ -8,17 +8,6 @@ /* Internal API */ /* - * Output the next line for a graph. - * This formats the next graph line into the specified strbuf. It is not - * terminated with a newline. - * - * Returns 1 if the line includes the current commit, and 0 otherwise. - * graph_next_line() will return 1 exactly once for each time - * graph_update() is called. - */ -static int graph_next_line(struct git_graph *graph, struct strbuf *sb); - -/* * Output a padding line in the graph. * This is similar to graph_next_line(). However, it is guaranteed to * never print the current commit line. Instead, if the commit line is @@ -73,7 +62,7 @@ enum graph_state { /* * The list of available column colors. */ -static char column_colors[][COLOR_MAXLEN] = { +static const char *column_colors_ansi[] = { GIT_COLOR_RED, GIT_COLOR_GREEN, GIT_COLOR_YELLOW, @@ -86,23 +75,33 @@ static char column_colors[][COLOR_MAXLEN] = { GIT_COLOR_BOLD_BLUE, GIT_COLOR_BOLD_MAGENTA, GIT_COLOR_BOLD_CYAN, + GIT_COLOR_RESET, }; -#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) +#define COLUMN_COLORS_ANSI_MAX (ARRAY_SIZE(column_colors_ansi) - 1) + +static const char **column_colors; +static unsigned short column_colors_max; -static const char *column_get_color_code(const struct column *c) +void graph_set_column_colors(const char **colors, unsigned short colors_max) { - return column_colors[c->color]; + column_colors = colors; + column_colors_max = colors_max; +} + +static const char *column_get_color_code(unsigned short color) +{ + return column_colors[color]; } static void strbuf_write_column(struct strbuf *sb, const struct column *c, char col_char) { - if (c->color < COLUMN_COLORS_MAX) - strbuf_addstr(sb, column_get_color_code(c)); + if (c->color < column_colors_max) + strbuf_addstr(sb, column_get_color_code(c->color)); strbuf_addch(sb, col_char); - if (c->color < COLUMN_COLORS_MAX) - strbuf_addstr(sb, GIT_COLOR_RESET); + if (c->color < column_colors_max) + strbuf_addstr(sb, column_get_color_code(column_colors_max)); } struct git_graph { @@ -226,6 +225,11 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void struct git_graph *graph_init(struct rev_info *opt) { struct git_graph *graph = xmalloc(sizeof(struct git_graph)); + + if (!column_colors) + graph_set_column_colors(column_colors_ansi, + COLUMN_COLORS_ANSI_MAX); + graph->commit = NULL; graph->revs = opt; graph->num_parents = 0; @@ -242,7 +246,7 @@ struct git_graph *graph_init(struct rev_info *opt) * always increment it for the first commit we output. * This way we start at 0 for the first commit. */ - graph->default_column_color = COLUMN_COLORS_MAX - 1; + graph->default_column_color = column_colors_max - 1; /* * Allocate a reasonably large default number of columns @@ -365,7 +369,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph) static unsigned short graph_get_current_column_color(const struct git_graph *graph) { if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF)) - return COLUMN_COLORS_MAX; + return column_colors_max; return graph->default_column_color; } @@ -375,7 +379,7 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra static void graph_increment_column_color(struct git_graph *graph) { graph->default_column_color = (graph->default_column_color + 1) % - COLUMN_COLORS_MAX; + column_colors_max; } static unsigned short graph_find_commit_color(const struct git_graph *graph, @@ -1143,7 +1147,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf graph_update_state(graph, GRAPH_PADDING); } -static int graph_next_line(struct git_graph *graph, struct strbuf *sb) +int graph_next_line(struct git_graph *graph, struct strbuf *sb) { switch (graph->state) { case GRAPH_PADDING: @@ -5,6 +5,23 @@ struct git_graph; /* + * Set up a custom scheme for column colors. + * + * The default column color scheme inserts ANSI color escapes to colorize + * the graph. The various color escapes are stored in an array of strings + * where each entry corresponds to a color, except for the last entry, + * which denotes the escape for resetting the color back to the default. + * When generating the graph, strings from this array are inserted before + * and after the various column characters. + * + * This function allows you to enable a custom array of color escapes. + * The 'colors_max' argument is the index of the last "reset" entry. + * + * This functions must be called BEFORE graph_init() is called. + */ +void graph_set_column_colors(const char **colors, unsigned short colors_max); + +/* * Create a new struct git_graph. */ struct git_graph *graph_init(struct rev_info *opt); @@ -32,6 +49,17 @@ void graph_update(struct git_graph *graph, struct commit *commit); */ int graph_is_commit_finished(struct git_graph const *graph); +/* + * Output the next line for a graph. + * This formats the next graph line into the specified strbuf. It is not + * terminated with a newline. + * + * Returns 1 if the line includes the current commit, and 0 otherwise. + * graph_next_line() will return 1 exactly once for each time + * graph_update() is called. + */ +int graph_next_line(struct git_graph *graph, struct strbuf *sb); + /* * graph_show_*: helper functions for printing to stdout @@ -23,10 +23,10 @@ #endif #if LIBCURL_VERSION_NUM < 0x070704 -#define curl_global_cleanup() do { /* nothing */ } while(0) +#define curl_global_cleanup() do { /* nothing */ } while (0) #endif #if LIBCURL_VERSION_NUM < 0x070800 -#define curl_global_init(a) do { /* nothing */ } while(0) +#define curl_global_init(a) do { /* nothing */ } while (0) #endif #if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000) diff --git a/imap-send.c b/imap-send.c index 1a577a0a09..71506a8dd3 100644 --- a/imap-send.c +++ b/imap-send.c @@ -543,9 +543,13 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, while (imap->literal_pending) get_cmd_result(ctx, NULL); - bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? - "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", - cmd->tag, cmd->cmd, cmd->cb.dlen); + if (!cmd->cb.data) + bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd); + else + bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n", + cmd->tag, cmd->cmd, cmd->cb.dlen, + CAP(LITERALPLUS) ? "+" : ""); + if (Verbose) { if (imap->num_in_progress) printf("(%d in progress) ", imap->num_in_progress); @@ -1086,7 +1090,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) int gai; char portstr[6]; - snprintf(portstr, sizeof(portstr), "%hu", srvc->port); + snprintf(portstr, sizeof(portstr), "%d", srvc->port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; diff --git a/merge-recursive.c b/merge-recursive.c index fb6aa4a551..638076ec6e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -20,6 +20,7 @@ #include "attr.h" #include "merge-recursive.h" #include "dir.h" +#include "submodule.h" static struct tree *shift_tree_object(struct tree *one, struct tree *two, const char *subtree_shift) @@ -136,16 +137,10 @@ static void output_commit_title(struct merge_options *o, struct commit *commit) if (parse_commit(commit) != 0) printf("(bad commit)\n"); else { - const char *s; - int len; - for (s = commit->buffer; *s; s++) - if (*s == '\n' && s[1] == '\n') { - s += 2; - break; - } - for (len = 0; s[len] && '\n' != s[len]; len++) - ; /* do nothing */ - printf("%.*s\n", len, s); + const char *title; + int len = find_commit_subject(commit->buffer, &title); + if (len) + printf("%.*s\n", len, title); } } } @@ -185,7 +180,7 @@ static int git_merge_trees(int index_only, opts.fn = threeway_merge; opts.src_index = &the_index; opts.dst_index = &the_index; - opts.msgs = get_porcelain_error_msgs(); + set_porcelain_error_msgs(opts.msgs, "merge"); init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -525,13 +520,15 @@ static void update_file_flags(struct merge_options *o, void *buf; unsigned long size; - if (S_ISGITLINK(mode)) + if (S_ISGITLINK(mode)) { /* * We may later decide to recursively descend into * the submodule directory and update its index * and/or work tree, but we do not do that now. */ + update_wd = 0; goto update_index; + } buf = read_sha1_file(sha, &type, &size); if (!buf) @@ -716,8 +713,8 @@ static struct merge_file_info merge_file(struct merge_options *o, free(result_buf.ptr); result.clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { - result.clean = 0; - hashcpy(result.sha, a->sha1); + result.clean = merge_submodule(result.sha, one->path, one->sha1, + a->sha1, b->sha1); } else if (S_ISLNK(a->mode)) { hashcpy(result.sha, a->sha1); @@ -806,7 +803,8 @@ static int process_renames(struct merge_options *o, struct string_list *b_renames) { int clean_merge = 1, i, j; - struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + struct string_list a_by_dst = STRING_LIST_INIT_NODUP; + struct string_list b_by_dst = STRING_LIST_INIT_NODUP; const struct rename *sre; for (i = 0; i < a_renames->nr; i++) { @@ -1178,26 +1176,48 @@ static int process_entry(struct merge_options *o, return clean_merge; } -struct unpack_trees_error_msgs get_porcelain_error_msgs(void) +void set_porcelain_error_msgs(const char **msgs, const char *cmd) { - struct unpack_trees_error_msgs msgs = { - /* would_overwrite */ - "Your local changes to '%s' would be overwritten by merge. Aborting.", - /* not_uptodate_file */ - "Your local changes to '%s' would be overwritten by merge. Aborting.", - /* not_uptodate_dir */ - "Updating '%s' would lose untracked files in it. Aborting.", - /* would_lose_untracked */ - "Untracked working tree file '%s' would be %s by merge. Aborting", - /* bind_overlap -- will not happen here */ - NULL, - }; - if (advice_commit_before_merge) { - msgs.would_overwrite = msgs.not_uptodate_file = - "Your local changes to '%s' would be overwritten by merge. Aborting.\n" - "Please, commit your changes or stash them before you can merge."; - } - return msgs; + const char *msg; + char *tmp; + const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches"; + if (advice_commit_before_merge) + msg = "Your local changes to the following files would be overwritten by %s:\n%%s" + "Please, commit your changes or stash them before you can %s."; + else + msg = "Your local changes to the following files would be overwritten by %s:\n%%s"; + tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2); + sprintf(tmp, msg, cmd, cmd2); + msgs[ERROR_WOULD_OVERWRITE] = tmp; + msgs[ERROR_NOT_UPTODATE_FILE] = tmp; + + msgs[ERROR_NOT_UPTODATE_DIR] = + "Updating the following directories would lose untracked files in it:\n%s"; + + if (advice_commit_before_merge) + msg = "The following untracked working tree files would be %s by %s:\n%%s" + "Please move or remove them before you can %s."; + else + msg = "The following untracked working tree files would be %s by %s:\n%%s"; + tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4); + sprintf(tmp, msg, "removed", cmd, cmd2); + msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp; + tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4); + sprintf(tmp, msg, "overwritten", cmd, cmd2); + msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp; + + /* + * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we + * cannot easily display it as a list. + */ + msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind."; + + msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] = + "Cannot update sparse checkout: the following entries are not up-to-date:\n%s"; + msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] = + "The following Working tree files would be overwritten by sparse checkout update:\n%s"; + msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] = + "The following Working tree files would be removed by sparse checkout update:\n%s"; } int merge_trees(struct merge_options *o, diff --git a/merge-recursive.h b/merge-recursive.h index b831293b38..08f9850367 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -23,8 +23,11 @@ struct merge_options { struct string_list current_directory_set; }; -/* Return a list of user-friendly error messages to be used by merge */ -struct unpack_trees_error_msgs get_porcelain_error_msgs(void); +/* + * Sets the list of user-friendly error messages to be used by the + * command "cmd" (either merge or checkout) + */ +void set_porcelain_error_msgs(const char **msgs, const char *cmd); /* merge_trees() but with recursive ancestor consolidation */ int merge_recursive(struct merge_options *o, @@ -877,14 +877,6 @@ void string_list_add_refs_from_colon_sep(struct string_list *list, strbuf_release(&globbuf); } -static int string_list_add_refs_from_list(struct string_list_item *item, - void *cb) -{ - struct string_list *list = cb; - string_list_add_refs_by_glob(list, item->string); - return 0; -} - static int notes_display_config(const char *k, const char *v, void *cb) { int *load_refs = cb; @@ -947,30 +939,18 @@ void init_notes(struct notes_tree *t, const char *notes_ref, load_subtree(t, &root_tree, t->root, 0); } -struct load_notes_cb_data { - int counter; - struct notes_tree **trees; -}; - -static int load_one_display_note_ref(struct string_list_item *item, - void *cb_data) -{ - struct load_notes_cb_data *c = cb_data; - struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); - init_notes(t, item->string, combine_notes_ignore, 0); - c->trees[c->counter++] = t; - return 0; -} - struct notes_tree **load_notes_trees(struct string_list *refs) { + struct string_list_item *item; + int counter = 0; struct notes_tree **trees; - struct load_notes_cb_data cb_data; trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *)); - cb_data.counter = 0; - cb_data.trees = trees; - for_each_string_list(refs, load_one_display_note_ref, &cb_data); - trees[cb_data.counter] = NULL; + for_each_string_list_item(item, refs) { + struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, item->string, combine_notes_ignore, 0); + trees[counter++] = t; + } + trees[counter] = NULL; return trees; } @@ -995,10 +975,12 @@ void init_display_notes(struct display_notes_opt *opt) git_config(notes_display_config, &load_config_refs); - if (opt && opt->extra_notes_refs) - for_each_string_list(opt->extra_notes_refs, - string_list_add_refs_from_list, - &display_notes_refs); + if (opt && opt->extra_notes_refs) { + struct string_list_item *item; + for_each_string_list_item(item, opt->extra_notes_refs) + string_list_add_refs_by_glob(&display_notes_refs, + item->string); + } display_notes_trees = load_notes_trees(&display_notes_refs); string_list_clear(&display_notes_refs, 0); diff --git a/pack-refs.c b/pack-refs.c index 7f43f8ac33..1290570260 100644 --- a/pack-refs.c +++ b/pack-refs.c @@ -60,6 +60,37 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, return 0; } +/* + * Remove empty parents, but spare refs/ and immediate subdirs. + * Note: munges *name. + */ +static void try_remove_empty_parents(char *name) +{ + char *p, *q; + int i; + p = name; + for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ + while (*p && *p != '/') + p++; + /* tolerate duplicate slashes; see check_ref_format() */ + while (*p == '/') + p++; + } + for (q = p; *q; q++) + ; + while (1) { + while (q > p && *q != '/') + q--; + while (q > p && *(q-1) == '/') + q--; + if (q == p) + break; + *q = '\0'; + if (rmdir(git_path("%s", name))) + break; + } +} + /* make sure nobody touched the ref, and unlink */ static void prune_ref(struct ref_to_prune *r) { @@ -68,6 +99,7 @@ static void prune_ref(struct ref_to_prune *r) if (lock) { unlink_or_warn(git_path("%s", r->name)); unlock_ref(lock); + try_remove_empty_parents(r->name); } } @@ -122,6 +122,44 @@ char *git_path(const char *fmt, ...) return cleanup_path(pathname); } +char *git_path_submodule(const char *path, const char *fmt, ...) +{ + char *pathname = get_pathname(); + struct strbuf buf = STRBUF_INIT; + const char *git_dir; + va_list args; + unsigned len; + + len = strlen(path); + if (len > PATH_MAX-100) + return bad_path; + + strbuf_addstr(&buf, path); + if (len && path[len-1] != '/') + strbuf_addch(&buf, '/'); + strbuf_addstr(&buf, ".git"); + + git_dir = read_gitfile_gently(buf.buf); + if (git_dir) { + strbuf_reset(&buf); + strbuf_addstr(&buf, git_dir); + } + strbuf_addch(&buf, '/'); + + if (buf.len >= PATH_MAX) + return bad_path; + memcpy(pathname, buf.buf, buf.len + 1); + + strbuf_release(&buf); + len = strlen(pathname); + + va_start(args, fmt); + len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args); + va_end(args); + if (len >= PATH_MAX) + return bad_path; + return cleanup_path(pathname); +} /* git_mkstemp() - create tmp file honoring TMPDIR variable */ int git_mkstemp(char *path, size_t len, const char *template) @@ -316,6 +354,8 @@ char *expand_user_path(const char *path) size_t username_len = first_slash - username; if (username_len == 0) { const char *home = getenv("HOME"); + if (!home) + goto return_null; strbuf_add(&user_path, home, strlen(home)); } else { struct passwd *pw = getpw_str(username, username_len); diff --git a/perl/Makefile b/perl/Makefile index 4ab21d61b8..a2ffb6402d 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -38,7 +38,7 @@ $(makfile): ../GIT-CFLAGS Makefile echo ' echo $(instdir_SQ)' >> $@ else $(makfile): Makefile.PL ../GIT-CFLAGS - $(PERL_PATH) $< PREFIX='$(prefix_SQ)' + $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' endif # this is just added comfort for calling make directly in perl dir diff --git a/read-cache.c b/read-cache.c index f1f789b7b8..1f42473e80 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1516,6 +1516,7 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce) int size = ondisk_ce_size(ce); struct ondisk_cache_entry *ondisk = xcalloc(1, size); char *name; + int result; ondisk->ctime.sec = htonl(ce->ce_ctime.sec); ondisk->mtime.sec = htonl(ce->ce_mtime.sec); @@ -1539,7 +1540,9 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce) name = ondisk->name; memcpy(name, ce->name, ce_namelen(ce)); - return ce_write(c, fd, ondisk, size); + result = ce_write(c, fd, ondisk, size); + free(ondisk); + return result; } int write_index(struct index_state *istate, int newfd) @@ -157,7 +157,7 @@ static struct cached_refs { char did_packed; struct ref_list *loose; struct ref_list *packed; -} cached_refs; +} cached_refs, submodule_refs; static struct ref_list *current_ref; static struct ref_list *extra_refs; @@ -229,23 +229,45 @@ void clear_extra_refs(void) extra_refs = NULL; } -static struct ref_list *get_packed_refs(void) +static struct ref_list *get_packed_refs(const char *submodule) { - if (!cached_refs.did_packed) { - FILE *f = fopen(git_path("packed-refs"), "r"); - cached_refs.packed = NULL; + const char *packed_refs_file; + struct cached_refs *refs; + + if (submodule) { + packed_refs_file = git_path_submodule(submodule, "packed-refs"); + refs = &submodule_refs; + free_ref_list(refs->packed); + } else { + packed_refs_file = git_path("packed-refs"); + refs = &cached_refs; + } + + if (!refs->did_packed || submodule) { + FILE *f = fopen(packed_refs_file, "r"); + refs->packed = NULL; if (f) { - read_packed_refs(f, &cached_refs); + read_packed_refs(f, refs); fclose(f); } - cached_refs.did_packed = 1; + refs->did_packed = 1; } - return cached_refs.packed; + return refs->packed; } -static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +static struct ref_list *get_ref_dir(const char *submodule, const char *base, + struct ref_list *list) { - DIR *dir = opendir(git_path("%s", base)); + DIR *dir; + const char *path; + + if (submodule) + path = git_path_submodule(submodule, "%s", base); + else + path = git_path("%s", base); + + + dir = opendir(path); if (dir) { struct dirent *de; @@ -261,6 +283,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) struct stat st; int flag; int namelen; + const char *refdir; if (de->d_name[0] == '.') continue; @@ -270,16 +293,27 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) if (has_extension(de->d_name, ".lock")) continue; memcpy(ref + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", ref), &st) < 0) + refdir = submodule + ? git_path_submodule(submodule, "%s", ref) + : git_path("%s", ref); + if (stat(refdir, &st) < 0) continue; if (S_ISDIR(st.st_mode)) { - list = get_ref_dir(ref, list); + list = get_ref_dir(submodule, ref, list); continue; } - if (!resolve_ref(ref, sha1, 1, &flag)) { + if (submodule) { hashclr(sha1); - flag |= REF_BROKEN; - } + flag = 0; + if (resolve_gitlink_ref(submodule, ref, sha1) < 0) { + hashclr(sha1); + flag |= REF_BROKEN; + } + } else + if (!resolve_ref(ref, sha1, 1, &flag)) { + hashclr(sha1); + flag |= REF_BROKEN; + } list = add_ref(ref, sha1, flag, list, NULL); } free(ref); @@ -322,10 +356,16 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) for_each_rawref(warn_if_dangling_symref, &data); } -static struct ref_list *get_loose_refs(void) +static struct ref_list *get_loose_refs(const char *submodule) { + if (submodule) { + free_ref_list(submodule_refs.loose); + submodule_refs.loose = get_ref_dir(submodule, "refs", NULL); + return submodule_refs.loose; + } + if (!cached_refs.did_loose) { - cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.loose = get_ref_dir(NULL, "refs", NULL); cached_refs.did_loose = 1; } return cached_refs.loose; @@ -459,7 +499,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * git_snpath(path, sizeof(path), "%s", ref); /* Special case: non-existing file. */ if (lstat(path, &st) < 0) { - struct ref_list *list = get_packed_refs(); + struct ref_list *list = get_packed_refs(NULL); while (list) { if (!strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); @@ -588,7 +628,7 @@ int peel_ref(const char *ref, unsigned char *sha1) return -1; if ((flag & REF_ISPACKED)) { - struct ref_list *list = get_packed_refs(); + struct ref_list *list = get_packed_refs(NULL); while (list) { if (!strcmp(list->name, ref)) { @@ -615,12 +655,12 @@ fallback: return -1; } -static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, - int flags, void *cb_data) +static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn, + int trim, int flags, void *cb_data) { int retval = 0; - struct ref_list *packed = get_packed_refs(); - struct ref_list *loose = get_loose_refs(); + struct ref_list *packed = get_packed_refs(submodule); + struct ref_list *loose = get_loose_refs(submodule); struct ref_list *extra; @@ -657,24 +697,54 @@ end_each: return retval; } -int head_ref(each_ref_fn fn, void *cb_data) + +static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; int flag; + if (submodule) { + if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0) + return fn("HEAD", sha1, 0, cb_data); + + return 0; + } + if (resolve_ref("HEAD", sha1, 1, &flag)) return fn("HEAD", sha1, flag, cb_data); + return 0; } +int head_ref(each_ref_fn fn, void *cb_data) +{ + return do_head_ref(NULL, fn, cb_data); +} + +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_head_ref(submodule, fn, cb_data); +} + int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, 0, cb_data); + return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data); +} + +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data); } int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data); + return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) @@ -682,19 +752,34 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data) return for_each_ref_in("refs/tags/", fn, cb_data); } +int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data); +} + int for_each_branch_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/heads/", fn, cb_data); } +int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data); +} + int for_each_remote_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/remotes/", fn, cb_data); } +int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data); +} + int for_each_replace_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); + return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data); } int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, @@ -734,7 +819,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) int for_each_rawref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, + return do_for_each_ref(NULL, "refs/", fn, 0, DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } @@ -958,7 +1043,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(ref, NULL, get_packed_refs(), 0)) { + !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) { last_errno = ENOTDIR; goto error_return; } @@ -1021,7 +1106,7 @@ static int repack_without_ref(const char *refname) int fd; int found = 0; - packed_ref_list = get_packed_refs(); + packed_ref_list = get_packed_refs(NULL); for (list = packed_ref_list; list; list = list->next) { if (!strcmp(refname, list->name)) { found = 1; @@ -1119,10 +1204,10 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) if (!symref) return error("refname %s not found", oldref); - if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0)) return 1; - if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0)) return 1; lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL); @@ -28,6 +28,14 @@ extern int for_each_replace_ref(each_ref_fn, void *); extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *); +extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data); +extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); + static inline const char *has_glob_specials(const char *pattern) { return strpbrk(pattern, "?*["); diff --git a/remote-curl.c b/remote-curl.c index 24fbb9a9b9..04d4813e41 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -528,11 +528,12 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->len = n; err |= post_rpc(rpc); } - strbuf_read(&rpc->result, client.out, 0); close(client.in); - close(client.out); client.in = -1; + strbuf_read(&rpc->result, client.out, 0); + + close(client.out); client.out = -1; err |= finish_command(&client); @@ -754,7 +754,7 @@ int for_each_remote(each_remote_fn fn, void *priv) void ref_remove_duplicates(struct ref *ref_map) { - struct string_list refs = { NULL, 0, 0, 0 }; + struct string_list refs = STRING_LIST_INIT_NODUP; struct string_list_item *item = NULL; struct ref *prev = NULL, *next = NULL; for (; ref_map; prev = ref_map, ref_map = next) { @@ -1704,7 +1704,7 @@ static int get_stale_heads_cb(const char *refname, struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) { struct ref *ref, *stale_refs = NULL; - struct string_list ref_names = { NULL, 0, 0, 0 }; + struct string_list ref_names = STRING_LIST_INIT_NODUP; struct stale_heads_info info; info.remote = remote; info.ref_names = &ref_names; @@ -426,8 +426,8 @@ static int update_paths(struct string_list *update) static int do_plain_rerere(struct string_list *rr, int fd) { - struct string_list conflict = { NULL, 0, 0, 1 }; - struct string_list update = { NULL, 0, 0, 1 }; + struct string_list conflict = STRING_LIST_INIT_DUP; + struct string_list update = STRING_LIST_INIT_DUP; int i; find_conflict(&conflict); @@ -547,7 +547,7 @@ int setup_rerere(struct string_list *merge_rr, int flags) int rerere(int flags) { - struct string_list merge_rr = { NULL, 0, 0, 1 }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; int fd; fd = setup_rerere(&merge_rr, flags); @@ -585,8 +585,8 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) int rerere_forget(const char **pathspec) { int i, fd; - struct string_list conflict = { NULL, 0, 0, 1 }; - struct string_list merge_rr = { NULL, 0, 0, 1 }; + struct string_list conflict = STRING_LIST_INIT_DUP; + struct string_list merge_rr = STRING_LIST_INIT_DUP; if (read_cache() < 0) return error("Could not read index"); diff --git a/resolve-undo.c b/resolve-undo.c index 174ebec9e5..72b46125b7 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -28,29 +28,25 @@ void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) ui->mode[stage - 1] = ce->ce_mode; } -static int write_one(struct string_list_item *item, void *cbdata) +void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) { - struct strbuf *sb = cbdata; - struct resolve_undo_info *ui = item->util; - int i; + struct string_list_item *item; + for_each_string_list_item(item, resolve_undo) { + struct resolve_undo_info *ui = item->util; + int i; - if (!ui) - return 0; - strbuf_addstr(sb, item->string); - strbuf_addch(sb, 0); - for (i = 0; i < 3; i++) - strbuf_addf(sb, "%o%c", ui->mode[i], 0); - for (i = 0; i < 3; i++) { - if (!ui->mode[i]) + if (!ui) continue; - strbuf_add(sb, ui->sha1[i], 20); + strbuf_addstr(sb, item->string); + strbuf_addch(sb, 0); + for (i = 0; i < 3; i++) + strbuf_addf(sb, "%o%c", ui->mode[i], 0); + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + strbuf_add(sb, ui->sha1[i], 20); + } } - return 0; -} - -void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) -{ - for_each_string_list(resolve_undo, write_one, sb); } struct string_list *resolve_undo_read(const char *data, unsigned long size) diff --git a/revision.c b/revision.c index f241f341c7..b1c18906ba 100644 --- a/revision.c +++ b/revision.c @@ -820,12 +820,12 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, cb->all_flags = flags; } -static void handle_refs(struct rev_info *revs, unsigned flags, - int (*for_each)(each_ref_fn, void *)) +static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags, + int (*for_each)(const char *, each_ref_fn, void *)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, flags); - for_each(handle_one_ref, &cb); + for_each(submodule, handle_one_ref, &cb); } static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) @@ -1435,14 +1435,14 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } -static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data) +static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) { - return for_each_ref_in("refs/bisect/bad", fn, cb_data); + return for_each_ref_in_submodule(submodule, "refs/bisect/bad", fn, cb_data); } -static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data) +static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) { - return for_each_ref_in("refs/bisect/good", fn, cb_data); + return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data); } static void append_prune_data(const char ***prune_data, const char **av) @@ -1484,9 +1484,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s { int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0; const char **prune_data = NULL; + const char *submodule = NULL; const char *optarg; int argcount; + if (opt) + submodule = opt->submodule; + /* First, search for "--" */ seen_dashdash = 0; for (i = 1; i < argc; i++) { @@ -1510,26 +1514,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s int opts; if (!strcmp(arg, "--all")) { - handle_refs(revs, flags, for_each_ref); - handle_refs(revs, flags, head_ref); + handle_refs(submodule, revs, flags, for_each_ref_submodule); + handle_refs(submodule, revs, flags, head_ref_submodule); continue; } if (!strcmp(arg, "--branches")) { - handle_refs(revs, flags, for_each_branch_ref); + handle_refs(submodule, revs, flags, for_each_branch_ref_submodule); continue; } if (!strcmp(arg, "--bisect")) { - handle_refs(revs, flags, for_each_bad_bisect_ref); - handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); + handle_refs(submodule, revs, flags, for_each_bad_bisect_ref); + handle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); revs->bisect = 1; continue; } if (!strcmp(arg, "--tags")) { - handle_refs(revs, flags, for_each_tag_ref); + handle_refs(submodule, revs, flags, for_each_tag_ref_submodule); continue; } if (!strcmp(arg, "--remotes")) { - handle_refs(revs, flags, for_each_remote_ref); + handle_refs(submodule, revs, flags, for_each_remote_ref_submodule); continue; } if ((argcount = parse_long_opt("glob", argv + i, &optarg))) { diff --git a/revision.h b/revision.h index 36fdf22b29..05659c64ac 100644 --- a/revision.h +++ b/revision.h @@ -151,6 +151,7 @@ extern volatile show_early_output_fn_t show_early_output; struct setup_revision_opt { const char *def; void (*tweak)(struct rev_info *, struct setup_revision_opt *); + const char *submodule; }; extern void init_revisions(struct rev_info *revs, const char *prefix); diff --git a/sha1_file.c b/sha1_file.c index e42ef96d45..0cd9435619 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2086,6 +2086,7 @@ void *read_sha1_file_repl(const unsigned char *sha1, { const unsigned char *repl = lookup_replace_object(sha1); void *data = read_object(repl, type, size); + char *path; /* die if we replaced an object with one that does not exist */ if (!data && repl != sha1) @@ -2093,8 +2094,16 @@ void *read_sha1_file_repl(const unsigned char *sha1, sha1_to_hex(repl), sha1_to_hex(sha1)); /* legacy behavior is to die on corrupted objects */ - if (!data && (has_loose_object(repl) || has_packed_and_bad(repl))) - die("object %s is corrupted", sha1_to_hex(repl)); + if (!data) { + if (has_loose_object(repl)) { + path = sha1_file_name(sha1); + die("loose object %s (stored in %s) is corrupted", sha1_to_hex(repl), path); + } + if (has_packed_and_bad(repl)) { + path = sha1_pack_name(sha1); + die("packed object %s (stored in %s) is corrupted", sha1_to_hex(repl), path); + } + } if (replacement) *replacement = repl; diff --git a/sha1_name.c b/sha1_name.c index 4f2af8da93..4af94fa598 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -659,6 +659,16 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) return get_short_sha1(name, len, sha1, 0); } +/* + * This interprets names like ':/Initial revision of "git"' by searching + * through history and returning the first commit whose message starts + * the given regular expression. + * + * For future extension, ':/!' is reserved. If you want to match a message + * beginning with a '!', you have to repeat the exclamation mark. + */ +#define ONELINE_SEEN (1u<<20) + static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { @@ -674,19 +684,10 @@ static int handle_one_ref(const char *path, if (object->type != OBJ_COMMIT) return 0; insert_by_date((struct commit *)object, list); + object->flags |= ONELINE_SEEN; return 0; } -/* - * This interprets names like ':/Initial revision of "git"' by searching - * through history and returning the first commit whose message matches - * the given regular expression. - * - * For future extension, ':/!' is reserved. If you want to match a message - * beginning with a '!', you have to repeat the exclamation mark. - */ - -#define ONELINE_SEEN (1u<<20) static int get_sha1_oneline(const char *prefix, unsigned char *sha1) { struct commit_list *list = NULL, *backup = NULL, *l; diff --git a/string-list.h b/string-list.h index 680d600d16..494693898b 100644 --- a/string-list.h +++ b/string-list.h @@ -12,6 +12,9 @@ struct string_list unsigned int strdup_strings:1; }; +#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 } +#define STRING_LIST_INIT_DUP { NULL, 0, 0, 1 } + void print_string_list(const struct string_list *p, const char *text); void string_list_clear(struct string_list *list, int free_util); @@ -20,10 +23,12 @@ void string_list_clear(struct string_list *list, int free_util); typedef void (*string_list_clear_func_t)(void *p, const char *str); void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc); -/* Use this function to iterate over each item */ +/* Use this function or the macro below to iterate over each item */ typedef int (*string_list_each_func_t)(struct string_list_item *, void *); int for_each_string_list(struct string_list *list, string_list_each_func_t, void *cb_data); +#define for_each_string_list_item(item,list) \ + for (item = (list)->items; item < (list)->items + (list)->nr; ++item) /* Use these functions only on sorted lists: */ int string_list_has_string(const struct string_list *list, const char *string); diff --git a/submodule.c b/submodule.c index 61cb6e21dd..91a4758747 100644 --- a/submodule.c +++ b/submodule.c @@ -6,6 +6,11 @@ #include "revision.h" #include "run-command.h" #include "diffcore.h" +#include "refs.h" +#include "string-list.h" + +struct string_list config_name_for_path; +struct string_list config_ignore_for_name; static int add_submodule_odb(const char *path) { @@ -46,16 +51,90 @@ done: return ret; } +void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, + const char *path) +{ + struct string_list_item *path_option, *ignore_option; + path_option = unsorted_string_list_lookup(&config_name_for_path, path); + if (path_option) { + ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); + if (ignore_option) + handle_ignore_submodules_arg(diffopt, ignore_option->util); + } +} + +static int submodule_config(const char *var, const char *value, void *cb) +{ + if (!prefixcmp(var, "submodule.")) + return parse_submodule_config_option(var, value); + return 0; +} + +void gitmodules_config(void) +{ + const char *work_tree = get_git_work_tree(); + if (work_tree) { + struct strbuf gitmodules_path = STRBUF_INIT; + strbuf_addstr(&gitmodules_path, work_tree); + strbuf_addstr(&gitmodules_path, "/.gitmodules"); + git_config_from_file(submodule_config, gitmodules_path.buf, NULL); + strbuf_release(&gitmodules_path); + } +} + +int parse_submodule_config_option(const char *var, const char *value) +{ + int len; + struct string_list_item *config; + struct strbuf submodname = STRBUF_INIT; + + var += 10; /* Skip "submodule." */ + + len = strlen(var); + if ((len > 5) && !strcmp(var + len - 5, ".path")) { + strbuf_add(&submodname, var, len - 5); + config = unsorted_string_list_lookup(&config_name_for_path, value); + if (config) + free(config->util); + else + config = string_list_append(&config_name_for_path, xstrdup(value)); + config->util = strbuf_detach(&submodname, NULL); + strbuf_release(&submodname); + } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) { + if (strcmp(value, "untracked") && strcmp(value, "dirty") && + strcmp(value, "all") && strcmp(value, "none")) { + warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); + return 0; + } + + strbuf_add(&submodname, var, len - 7); + config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf); + if (config) + free(config->util); + else + config = string_list_append(&config_ignore_for_name, + strbuf_detach(&submodname, NULL)); + strbuf_release(&submodname); + config->util = xstrdup(value); + return 0; + } + return 0; +} + void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { + DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES); + DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); + DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES); + if (!strcmp(arg, "all")) DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); else if (!strcmp(arg, "untracked")) DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); else if (!strcmp(arg, "dirty")) DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES); - else + else if (strcmp(arg, "none")) die("bad --ignore-submodules argument: %s", arg); } @@ -218,3 +297,163 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) strbuf_release(&buf); return dirty_submodule; } + +static int find_first_merges(struct object_array *result, const char *path, + struct commit *a, struct commit *b) +{ + int i, j; + struct object_array merges; + struct commit *commit; + int contains_another; + + char merged_revision[42]; + const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path", + "--all", merged_revision, NULL }; + struct rev_info revs; + struct setup_revision_opt rev_opts; + + memset(&merges, 0, sizeof(merges)); + memset(result, 0, sizeof(struct object_array)); + memset(&rev_opts, 0, sizeof(rev_opts)); + + /* get all revisions that merge commit a */ + snprintf(merged_revision, sizeof(merged_revision), "^%s", + sha1_to_hex(a->object.sha1)); + init_revisions(&revs, NULL); + rev_opts.submodule = path; + setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts); + + /* save all revisions from the above list that contain b */ + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + while ((commit = get_revision(&revs)) != NULL) { + struct object *o = &(commit->object); + if (in_merge_bases(b, &commit, 1)) + add_object_array(o, NULL, &merges); + } + + /* Now we've got all merges that contain a and b. Prune all + * merges that contain another found merge and save them in + * result. + */ + for (i = 0; i < merges.nr; i++) { + struct commit *m1 = (struct commit *) merges.objects[i].item; + + contains_another = 0; + for (j = 0; j < merges.nr; j++) { + struct commit *m2 = (struct commit *) merges.objects[j].item; + if (i != j && in_merge_bases(m2, &m1, 1)) { + contains_another = 1; + break; + } + } + + if (!contains_another) + add_object_array(merges.objects[i].item, + merges.objects[i].name, result); + } + + free(merges.objects); + return result->nr; +} + +static void print_commit(struct commit *commit) +{ + struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; + format_commit_message(commit, " %h: %m %s", &sb, &ctx); + fprintf(stderr, "%s\n", sb.buf); + strbuf_release(&sb); +} + +#define MERGE_WARNING(path, msg) \ + warning("Failed to merge submodule %s (%s)", path, msg); + +int merge_submodule(unsigned char result[20], const char *path, + const unsigned char base[20], const unsigned char a[20], + const unsigned char b[20]) +{ + struct commit *commit_base, *commit_a, *commit_b; + int parent_count; + struct object_array merges; + + int i; + + /* store a in result in case we fail */ + hashcpy(result, a); + + /* we can not handle deletion conflicts */ + if (is_null_sha1(base)) + return 0; + if (is_null_sha1(a)) + return 0; + if (is_null_sha1(b)) + return 0; + + if (add_submodule_odb(path)) { + MERGE_WARNING(path, "not checked out"); + return 0; + } + + if (!(commit_base = lookup_commit_reference(base)) || + !(commit_a = lookup_commit_reference(a)) || + !(commit_b = lookup_commit_reference(b))) { + MERGE_WARNING(path, "commits not present"); + return 0; + } + + /* check whether both changes are forward */ + if (!in_merge_bases(commit_base, &commit_a, 1) || + !in_merge_bases(commit_base, &commit_b, 1)) { + MERGE_WARNING(path, "commits don't follow merge-base"); + return 0; + } + + /* Case #1: a is contained in b or vice versa */ + if (in_merge_bases(commit_a, &commit_b, 1)) { + hashcpy(result, b); + return 1; + } + if (in_merge_bases(commit_b, &commit_a, 1)) { + hashcpy(result, a); + return 1; + } + + /* + * Case #2: There are one or more merges that contain a and b in + * the submodule. If there is only one, then present it as a + * suggestion to the user, but leave it marked unmerged so the + * user needs to confirm the resolution. + */ + + /* find commit which merges them */ + parent_count = find_first_merges(&merges, path, commit_a, commit_b); + switch (parent_count) { + case 0: + MERGE_WARNING(path, "merge following commits not found"); + break; + + case 1: + MERGE_WARNING(path, "not fast-forward"); + fprintf(stderr, "Found a possible merge resolution " + "for the submodule:\n"); + print_commit((struct commit *) merges.objects[0].item); + fprintf(stderr, + "If this is correct simply add it to the index " + "for example\n" + "by using:\n\n" + " git update-index --cacheinfo 160000 %s \"%s\"\n\n" + "which will accept this suggestion.\n", + sha1_to_hex(merges.objects[0].item->sha1), path); + break; + + default: + MERGE_WARNING(path, "multiple merges found"); + for (i = 0; i < merges.nr; i++) + print_commit((struct commit *) merges.objects[i].item); + } + + free(merges.objects); + return 0; +} diff --git a/submodule.h b/submodule.h index 6fd3bb4070..386f410a66 100644 --- a/submodule.h +++ b/submodule.h @@ -3,11 +3,17 @@ struct diff_options; +void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, + const char *path); +void gitmodules_config(); +int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset); unsigned is_submodule_modified(const char *path, int ignore_untracked); +int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], + const unsigned char a[20], const unsigned char b[20]); #endif diff --git a/t/.gitignore b/t/.gitignore index 7dcbb232cd..4e731dc1e3 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -1,2 +1,3 @@ /trash directory* /test-results +/.prove diff --git a/t/Makefile b/t/Makefile index cf5f9e2e1e..819b936870 100644 --- a/t/Makefile +++ b/t/Makefile @@ -30,6 +30,7 @@ clean: $(RM) -r 'trash directory'.* test-results $(RM) t????/cvsroot/CVSROOT/?* $(RM) -r valgrind/bin + $(RM) .prove aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index c3f6676ca2..92d6d31942 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -16,7 +16,6 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree -PERL=${PERL:-perl} svn >/dev/null 2>&1 if test $? -ne 1 @@ -30,7 +29,7 @@ export svnrepo svnconf=$PWD/svnconf export svnconf -$PERL -w -e " +"$PERL_PATH" -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); @@ -130,7 +129,7 @@ stop_httpd () { } convert_to_rev_db () { - $PERL -w -- - "$@" <<\EOF + "$PERL_PATH" -w -- - "$@" <<\EOF use strict; @ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>"; open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 0c562bb820..93ca84f9e6 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -359,7 +359,7 @@ test_expect_success \ test_expect_success \ 'a/b (untracked) vs a, plus c/d case test.' \ - '! git read-tree -u -m "$treeH" "$treeM" && + 'test_must_fail git read-tree -u -m "$treeH" "$treeM" && git ls-files --stage && test -f a/b' diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 62246dbf95..9a07de1a5b 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -1,16 +1,30 @@ #!/bin/sh -test_description='sparse checkout tests' +test_description='sparse checkout tests + +* (tag: removed, master) removed +| D sub/added +* (HEAD, tag: top) modified and added +| M init.t +| A sub/added +* (tag: init) init + A init.t +' . ./test-lib.sh -cat >expected <<EOF -100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added -EOF test_expect_success 'setup' ' + cat >expected <<-\EOF && + 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added + EOF + cat >expected.swt <<-\EOF && + H init.t + H sub/added + EOF + test_commit init && - echo modified >> init.t && + echo modified >>init.t && mkdir sub && touch sub/added && git add init.t sub/added && @@ -20,26 +34,22 @@ test_expect_success 'setup' ' git commit -m removed && git tag removed && git checkout top && - git ls-files --stage > result && + git ls-files --stage >result && test_cmp expected result ' -cat >expected.swt <<EOF -H init.t -H sub/added -EOF test_expect_success 'read-tree without .git/info/sparse-checkout' ' git read-tree -m -u HEAD && - git ls-files --stage > result && + git ls-files --stage >result && test_cmp expected result && - git ls-files -t > result && + git ls-files -t >result && test_cmp expected.swt result ' test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' - echo > .git/info/sparse-checkout + echo >.git/info/sparse-checkout git read-tree -m -u HEAD && - git ls-files -t > result && + git ls-files -t >result && test_cmp expected.swt result && test -f init.t && test -f sub/added @@ -47,9 +57,9 @@ test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' git config core.sparsecheckout true && - echo > .git/info/sparse-checkout && + echo >.git/info/sparse-checkout && git read-tree --no-sparse-checkout -m -u HEAD && - git ls-files -t > result && + git ls-files -t >result && test_cmp expected.swt result && test -f init.t && test -f sub/added @@ -57,94 +67,113 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse- test_expect_success 'read-tree with empty .git/info/sparse-checkout' ' git config core.sparsecheckout true && - echo > .git/info/sparse-checkout && + echo >.git/info/sparse-checkout && test_must_fail git read-tree -m -u HEAD && - git ls-files --stage > result && + git ls-files --stage >result && test_cmp expected result && - git ls-files -t > result && + git ls-files -t >result && test_cmp expected.swt result && test -f init.t && test -f sub/added ' -cat >expected.swt <<EOF -S init.t -H sub/added -EOF test_expect_success 'match directories with trailing slash' ' + cat >expected.swt-noinit <<-\EOF && + S init.t + H sub/added + EOF + echo sub/ > .git/info/sparse-checkout && git read-tree -m -u HEAD && git ls-files -t > result && - test_cmp expected.swt result && + test_cmp expected.swt-noinit result && test ! -f init.t && test -f sub/added ' -cat >expected.swt <<EOF -H init.t -H sub/added -EOF test_expect_failure 'match directories without trailing slash' ' - echo init.t > .git/info/sparse-checkout && - echo sub >> .git/info/sparse-checkout && + echo init.t >.git/info/sparse-checkout && + echo sub >>.git/info/sparse-checkout && git read-tree -m -u HEAD && - git ls-files -t > result && + git ls-files -t >result && test_cmp expected.swt result && test ! -f init.t && test -f sub/added ' -cat >expected.swt <<EOF -H init.t -S sub/added -EOF test_expect_success 'checkout area changes' ' - echo init.t > .git/info/sparse-checkout && + cat >expected.swt-nosub <<-\EOF && + H init.t + S sub/added + EOF + + echo init.t >.git/info/sparse-checkout && git read-tree -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && + git ls-files -t >result && + test_cmp expected.swt-nosub result && test -f init.t && test ! -f sub/added ' test_expect_success 'read-tree updates worktree, absent case' ' - echo sub/added > .git/info/sparse-checkout && + echo sub/added >.git/info/sparse-checkout && git checkout -f top && git read-tree -m -u HEAD^ && test ! -f init.t ' test_expect_success 'read-tree updates worktree, dirty case' ' - echo sub/added > .git/info/sparse-checkout && + echo sub/added >.git/info/sparse-checkout && git checkout -f top && - echo dirty > init.t && + echo dirty >init.t && git read-tree -m -u HEAD^ && grep -q dirty init.t && rm init.t ' test_expect_success 'read-tree removes worktree, dirty case' ' - echo init.t > .git/info/sparse-checkout && + echo init.t >.git/info/sparse-checkout && git checkout -f top && - echo dirty > added && + echo dirty >added && git read-tree -m -u HEAD^ && grep -q dirty added ' test_expect_success 'read-tree adds to worktree, absent case' ' - echo init.t > .git/info/sparse-checkout && + echo init.t >.git/info/sparse-checkout && git checkout -f removed && git read-tree -u -m HEAD^ && test ! -f sub/added ' test_expect_success 'read-tree adds to worktree, dirty case' ' - echo init.t > .git/info/sparse-checkout && + echo init.t >.git/info/sparse-checkout && git checkout -f removed && mkdir sub && - echo dirty > sub/added && + echo dirty >sub/added && git read-tree -u -m HEAD^ && grep -q dirty sub/added ' +test_expect_success 'index removal and worktree narrowing at the same time' ' + >empty && + echo init.t >.git/info/sparse-checkout && + echo sub/added >>.git/info/sparse-checkout && + git checkout -f top && + echo init.t >.git/info/sparse-checkout && + git checkout removed && + git ls-files sub/added >result && + test ! -f sub/added && + test_cmp empty result +' + +test_expect_success 'read-tree --reset removes outside worktree' ' + >empty && + echo init.t >.git/info/sparse-checkout && + git checkout -f top && + git reset --hard removed && + git ls-files sub/added >result && + test_cmp empty result +' + test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 64f05080b6..074f2f2e3e 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -707,19 +707,41 @@ test_expect_success 'set --path' ' git config --path path.trailingtilde "foo~" && test_cmp expect .git/config' +if test "${HOME+set}" +then + test_set_prereq HOMEVAR +fi + cat >expect <<EOF $HOME/ /dev/null foo~ EOF -test_expect_success 'get --path' ' +test_expect_success HOMEVAR 'get --path' ' git config --get --path path.home > result && git config --get --path path.normal >> result && git config --get --path path.trailingtilde >> result && test_cmp expect result ' +cat >expect <<\EOF +/dev/null +foo~ +EOF + +test_expect_success 'get --path copes with unset $HOME' ' + ( + unset HOME; + test_must_fail git config --get --path path.home \ + >result 2>msg && + git config --get --path path.normal >>result && + git config --get --path path.trailingtilde >>result + ) && + grep "[Ff]ailed to expand.*~/" msg && + test_cmp expect result +' + rm .git/config git config quote.leading " test" diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index eb45afb018..782e75d000 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -41,6 +41,23 @@ test_expect_success "check-ref-format --branch @{-1}" ' refname2=$(git check-ref-format --branch @{-2}) && test "$refname2" = master' +test_expect_success 'check-ref-format --branch from subdir' ' + mkdir subdir && + + T=$(git write-tree) && + sha1=$(echo A | git commit-tree $T) && + git update-ref refs/heads/master $sha1 && + git update-ref refs/remotes/origin/master $sha1 + git checkout master && + git checkout origin/master && + git checkout master && + refname=$( + cd subdir && + git check-ref-format --branch @{-1} + ) && + test "$refname" = "$sha1" +' + valid_ref_normalized() { test_expect_success "ref name '$1' simplifies to '$2'" " refname=\$(git check-ref-format --print '$1') && diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh new file mode 100755 index 0000000000..fa69016381 --- /dev/null +++ b/t/t2018-checkout-branch.sh @@ -0,0 +1,172 @@ +#!/bin/sh + +test_description='checkout ' + +. ./test-lib.sh + +# Arguments: <branch> <sha> [<checkout options>] +# +# Runs "git checkout" to switch to <branch>, testing that +# +# 1) we are on the specified branch, <branch>; +# 2) HEAD is <sha>; if <sha> is not specified, the old HEAD is used. +# +# If <checkout options> is not specified, "git checkout" is run with -b. +do_checkout() { + exp_branch=$1 && + exp_ref="refs/heads/$exp_branch" && + + # if <sha> is not specified, use HEAD. + exp_sha=${2:-$(git rev-parse --verify HEAD)} && + + # default options for git checkout: -b + if [ -z "$3" ]; then + opts="-b" + else + opts="$3" + fi + + git checkout $opts $exp_branch $exp_sha && + + test $exp_ref = $(git rev-parse --symbolic-full-name HEAD) && + test $exp_sha = $(git rev-parse --verify HEAD) +} + +test_dirty_unmergeable() { + ! git diff --exit-code >/dev/null +} + +setup_dirty_unmergeable() { + echo >>file1 change2 +} + +test_dirty_mergeable() { + ! git diff --cached --exit-code >/dev/null +} + +setup_dirty_mergeable() { + echo >file2 file2 && + git add file2 +} + +test_expect_success 'setup' ' + test_commit initial file1 && + HEAD1=$(git rev-parse --verify HEAD) && + + test_commit change1 file1 && + HEAD2=$(git rev-parse --verify HEAD) && + + git branch -m branch1 +' + +test_expect_success 'checkout -b to a new branch, set to HEAD' ' + do_checkout branch2 +' + +test_expect_success 'checkout -b to a new branch, set to an explicit ref' ' + git checkout branch1 && + git branch -D branch2 && + + do_checkout branch2 $HEAD1 +' + +test_expect_success 'checkout -b to a new branch with unmergeable changes fails' ' + git checkout branch1 && + + # clean up from previous test + git branch -D branch2 && + + setup_dirty_unmergeable && + test_must_fail do_checkout branch2 $HEAD1 && + test_dirty_unmergeable +' + +test_expect_success 'checkout -f -b to a new branch with unmergeable changes discards changes' ' + # still dirty and on branch1 + do_checkout branch2 $HEAD1 "-f -b" && + test_must_fail test_dirty_unmergeable +' + +test_expect_success 'checkout -b to a new branch preserves mergeable changes' ' + git checkout branch1 && + + # clean up from previous test + git branch -D branch2 && + + setup_dirty_mergeable && + do_checkout branch2 $HEAD1 && + test_dirty_mergeable +' + +test_expect_success 'checkout -f -b to a new branch with mergeable changes discards changes' ' + # clean up from previous test + git reset --hard && + + git checkout branch1 && + + # clean up from previous test + git branch -D branch2 && + + setup_dirty_mergeable && + do_checkout branch2 $HEAD1 "-f -b" && + test_must_fail test_dirty_mergeable +' + +test_expect_success 'checkout -b to an existing branch fails' ' + git reset --hard HEAD && + + test_must_fail do_checkout branch2 $HEAD2 +' + +test_expect_success 'checkout -B to an existing branch resets branch to HEAD' ' + git checkout branch1 && + + do_checkout branch2 "" -B +' + +test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' ' + git checkout $(git rev-parse --verify HEAD) && + + do_checkout branch2 "" -B +' + +test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' ' + git checkout branch1 && + + do_checkout branch2 $HEAD1 -B +' + +test_expect_success 'checkout -B to an existing branch with unmergeable changes fails' ' + git checkout branch1 && + + setup_dirty_unmergeable && + test_must_fail do_checkout branch2 $HEAD1 -B && + test_dirty_unmergeable +' + +test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' ' + # still dirty and on branch1 + do_checkout branch2 $HEAD1 "-f -B" && + test_must_fail test_dirty_unmergeable +' + +test_expect_success 'checkout -B to an existing branch preserves mergeable changes' ' + git checkout branch1 && + + setup_dirty_mergeable && + do_checkout branch2 $HEAD1 -B && + test_dirty_mergeable +' + +test_expect_success 'checkout -f -B to an existing branch with mergeable changes discards changes' ' + # clean up from previous test + git reset --hard && + + git checkout branch1 && + + setup_dirty_mergeable && + do_checkout branch2 $HEAD1 "-f -B" && + test_must_fail test_dirty_mergeable +' + +test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index d541544537..efe2900a37 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -294,7 +294,7 @@ test_expect_success 'fail if the index has unresolved entries' ' grep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "Your local changes to .* would be overwritten by merge." out + grep "Your local changes to the following files would be overwritten by merge:" out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 525174013c..cd04361df8 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -60,6 +60,12 @@ test_expect_success 'see if git pack-refs --prune remove ref files' ' ! test -f .git/refs/heads/f ' +test_expect_success 'see if git pack-refs --prune removes empty dirs' ' + git branch r/s/t && + git pack-refs --all --prune && + ! test -e .git/refs/heads/r +' + test_expect_success \ 'git branch g should work when git branch g/h has been deleted' \ 'git branch g/h && diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 1d82f79ee0..96b75813d7 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -299,7 +299,7 @@ cat expect-F >> expect-rm-F test_expect_success 'verify note removal with -F /dev/null' ' git log -4 > output && test_cmp expect-rm-F output && - ! git notes show + test_must_fail git notes show ' test_expect_success 'do not create empty note with -m "" (setup)' ' @@ -309,7 +309,7 @@ test_expect_success 'do not create empty note with -m "" (setup)' ' test_expect_success 'verify non-creation of note with -m ""' ' git log -4 > output && test_cmp expect-rm-F output && - ! git notes show + test_must_fail git notes show ' cat > expect-combine_m_and_F << EOF @@ -357,7 +357,7 @@ cat expect-multiline >> expect-rm-remove test_expect_success 'verify note removal with "git notes remove"' ' git log -4 > output && test_cmp expect-rm-remove output && - ! git notes show HEAD^ + test_must_fail git notes show HEAD^ ' cat > expect << EOF diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh index b4554041b4..c4282179b3 100755 --- a/t/t3306-notes-prune.sh +++ b/t/t3306-notes-prune.sh @@ -67,7 +67,7 @@ test_expect_success 'remove some commits' ' test_expect_success 'verify that commits are gone' ' - ! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + test_must_fail git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 && git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f ' @@ -106,7 +106,7 @@ test_expect_success 'prune notes' ' test_expect_success 'verify that notes are gone' ' - ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f ' @@ -130,8 +130,8 @@ test_expect_success 'prune -v notes' ' test_expect_success 'verify that notes are gone' ' - ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && - ! git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + test_must_fail git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f ' diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index d98c7b5571..349eebd542 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -14,140 +14,165 @@ GIT_AUTHOR_NAME=author@name GIT_AUTHOR_EMAIL=bogus@email@address export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL -test_expect_success \ - 'prepare repository with topic branches' \ - 'git config core.logAllRefUpdates true && - echo First > A && - git update-index --add A && - git commit -m "Add A." && - git checkout -b my-topic-branch && - echo Second > B && - git update-index --add B && - git commit -m "Add B." && - git checkout -f master && - echo Third >> A && - git update-index A && - git commit -m "Modify A." && - git checkout -b side my-topic-branch && - echo Side >> C && - git add C && - git commit -m "Add C" && - git checkout -b nonlinear my-topic-branch && - echo Edit >> B && - git add B && - git commit -m "Modify B" && - git merge side && - git checkout -b upstream-merged-nonlinear && - git merge master && - git checkout -f my-topic-branch && - git tag topic +test_expect_success 'prepare repository with topic branches' ' + git config core.logAllRefUpdates true && + echo First >A && + git update-index --add A && + git commit -m "Add A." && + git checkout -b force-3way && + echo Dummy >Y && + git update-index --add Y && + git commit -m "Add Y." && + git checkout -b filemove && + git reset --soft master && + mkdir D && + git mv A D/A && + git commit -m "Move A." && + git checkout -b my-topic-branch master && + echo Second >B && + git update-index --add B && + git commit -m "Add B." && + git checkout -f master && + echo Third >>A && + git update-index A && + git commit -m "Modify A." && + git checkout -b side my-topic-branch && + echo Side >>C && + git add C && + git commit -m "Add C" && + git checkout -b nonlinear my-topic-branch && + echo Edit >>B && + git add B && + git commit -m "Modify B" && + git merge side && + git checkout -b upstream-merged-nonlinear && + git merge master && + git checkout -f my-topic-branch && + git tag topic ' test_expect_success 'rebase on dirty worktree' ' - echo dirty >> A && - test_must_fail git rebase master' + echo dirty >>A && + test_must_fail git rebase master +' test_expect_success 'rebase on dirty cache' ' - git add A && - test_must_fail git rebase master' + git add A && + test_must_fail git rebase master +' test_expect_success 'rebase against master' ' - git reset --hard HEAD && - git rebase master' + git reset --hard HEAD && + git rebase master +' test_expect_success 'rebase against master twice' ' - git rebase master >out && - grep "Current branch my-topic-branch is up to date" out + git rebase master >out && + grep "Current branch my-topic-branch is up to date" out ' test_expect_success 'rebase against master twice with --force' ' - git rebase --force-rebase master >out && - grep "Current branch my-topic-branch is up to date, rebase forced" out + git rebase --force-rebase master >out && + grep "Current branch my-topic-branch is up to date, rebase forced" out ' test_expect_success 'rebase against master twice from another branch' ' - git checkout my-topic-branch^ && - git rebase master my-topic-branch >out && - grep "Current branch my-topic-branch is up to date" out + git checkout my-topic-branch^ && + git rebase master my-topic-branch >out && + grep "Current branch my-topic-branch is up to date" out ' test_expect_success 'rebase fast-forward to master' ' - git checkout my-topic-branch^ && - git rebase my-topic-branch >out && - grep "Fast-forwarded HEAD to my-topic-branch" out + git checkout my-topic-branch^ && + git rebase my-topic-branch >out && + grep "Fast-forwarded HEAD to my-topic-branch" out ' -test_expect_success \ - 'the rebase operation should not have destroyed author information' \ - '! (git log | grep "Author:" | grep "<>")' +test_expect_success 'the rebase operation should not have destroyed author information' ' + ! (git log | grep "Author:" | grep "<>") +' -test_expect_success \ - 'the rebase operation should not have destroyed author information (2)' \ - "git log -1 | grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'" +test_expect_success 'the rebase operation should not have destroyed author information (2)' " + git log -1 | + grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>' +" test_expect_success 'HEAD was detached during rebase' ' - test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1}) + test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1}) ' test_expect_success 'rebase after merge master' ' - git reset --hard topic && - git merge master && - git rebase master && - ! (git show | grep "^Merge:") + git reset --hard topic && + git merge master && + git rebase master && + ! (git show | grep "^Merge:") ' test_expect_success 'rebase of history with merges is linearized' ' - git checkout nonlinear && - test 4 = $(git rev-list master.. | wc -l) && - git rebase master && - test 3 = $(git rev-list master.. | wc -l) + git checkout nonlinear && + test 4 = $(git rev-list master.. | wc -l) && + git rebase master && + test 3 = $(git rev-list master.. | wc -l) ' -test_expect_success \ - 'rebase of history with merges after upstream merge is linearized' ' - git checkout upstream-merged-nonlinear && - test 5 = $(git rev-list master.. | wc -l) && - git rebase master && - test 3 = $(git rev-list master.. | wc -l) +test_expect_success 'rebase of history with merges after upstream merge is linearized' ' + git checkout upstream-merged-nonlinear && + test 5 = $(git rev-list master.. | wc -l) && + git rebase master && + test 3 = $(git rev-list master.. | wc -l) ' test_expect_success 'rebase a single mode change' ' - git checkout master && - echo 1 > X && - git add X && - test_tick && - git commit -m prepare && - git checkout -b modechange HEAD^ && - echo 1 > X && - git add X && - test_chmod +x A && - test_tick && - git commit -m modechange && - GIT_TRACE=1 git rebase master + git checkout master && + echo 1 >X && + git add X && + test_tick && + git commit -m prepare && + git checkout -b modechange HEAD^ && + echo 1 >X && + git add X && + test_chmod +x A && + test_tick && + git commit -m modechange && + GIT_TRACE=1 git rebase master +' + +test_expect_success 'rebase is not broken by diff.renames' ' + git config diff.renames copies && + test_when_finished "git config --unset diff.renames" && + git checkout filemove && + GIT_TRACE=1 git rebase force-3way +' + +test_expect_success 'setup: recover' ' + test_might_fail git rebase --abort && + git reset --hard && + git checkout modechange ' test_expect_success 'Show verbose error when HEAD could not be detached' ' - : > B && - test_must_fail git rebase topic 2> output.err > output.out && - grep "Untracked working tree file .B. would be overwritten" output.err + >B && + test_must_fail git rebase topic 2>output.err >output.out && + grep "The following untracked working tree files would be overwritten by checkout:" output.err && + grep B output.err ' rm -f B test_expect_success 'dump usage when upstream arg is missing' ' - git checkout -b usage topic && - test_must_fail git rebase 2>error1 && - grep "[Uu]sage" error1 && - test_must_fail git rebase --abort 2>error2 && - grep "No rebase in progress" error2 && - test_must_fail git rebase --onto master 2>error3 && - grep "[Uu]sage" error3 && - ! grep "can.t shift" error3 + git checkout -b usage topic && + test_must_fail git rebase 2>error1 && + grep "[Uu]sage" error1 && + test_must_fail git rebase --abort 2>error2 && + grep "No rebase in progress" error2 && + test_must_fail git rebase --onto master 2>error3 && + grep "[Uu]sage" error3 && + ! grep "can.t shift" error3 ' test_expect_success 'rebase -q is quiet' ' - git checkout -b quiet topic && - git rebase -q master > output.out 2>&1 && - test ! -s output.out + git checkout -b quiet topic && + git rebase -q master >output.out 2>&1 && + test ! -s output.out ' test_expect_success 'Rebase a commit that sprinkles CRs in' ' diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 7b7d07269a..2bea65634a 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -74,6 +74,15 @@ test_expect_success 'rebase the other way' ' git rebase --merge side ' +test_expect_success 'rebase -Xtheirs' ' + git checkout -b conflicting master~2 && + echo "AB $T" >> original && + git commit -mconflicting original && + git rebase -Xtheirs master && + grep AB original && + ! grep 11 original +' + test_expect_success 'merge and rebase should match' ' git diff-tree -r test-rebase test-merge >difference && if test -s difference diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 47ca88fc52..3af3f603fb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -150,8 +150,9 @@ test_expect_success 'abort with error when new base cannot be checked out' ' git rm --cached file1 && git commit -m "remove file in base" && test_must_fail git rebase -i master > output 2>&1 && - grep "Untracked working tree file .file1. would be overwritten" \ + grep "The following untracked working tree files would be overwritten by checkout:" \ output && + grep "file1" output && ! test -d .git/rebase-merge && git reset --hard HEAD^ ' @@ -637,13 +638,19 @@ test_expect_success 'set up commits with funny messages' ' git commit -a -m "end with slash\\" && echo >>file1 && test_tick && + git commit -a -m "something (\000) that looks like octal" && + echo >>file1 && + test_tick && + git commit -a -m "something (\n) that looks like a newline" && + echo >>file1 && + test_tick && git commit -a -m "another commit" ' test_expect_success 'rebase-i history with funny messages' ' git rev-list A..funny >expect && test_tick && - FAKE_LINES="1 2" git rebase -i A && + FAKE_LINES="1 2 3 4" git rebase -i A && git rev-list A.. >actual && test_cmp expect actual ' diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh index c49143a1a4..6f73b95558 100755 --- a/t/t3410-rebase-preserve-dropped-merges.sh +++ b/t/t3410-rebase-preserve-dropped-merges.sh @@ -43,11 +43,11 @@ test_expect_success 'setup' ' # G2 = same changes as G test_expect_success 'skip same-resolution merges with -p' ' git checkout H && - ! git merge E && + test_must_fail git merge E && test_commit L file1 23 && git checkout I && test_commit G2 file1 3 && - ! git merge E && + test_must_fail git merge E && test_commit J file1 23 && test_commit K file7 file7 && git rebase -i -p L && @@ -65,11 +65,11 @@ test_expect_success 'skip same-resolution merges with -p' ' # G2 = different changes as G test_expect_success 'keep different-resolution merges with -p' ' git checkout H && - ! git merge E && + test_must_fail git merge E && test_commit L2 file1 23 && git checkout I && test_commit G3 file1 4 && - ! git merge E && + test_must_fail git merge E && test_commit J2 file1 24 && test_commit K2 file7 file7 && test_must_fail git rebase -i -p L2 && diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh new file mode 100755 index 0000000000..3b0d27350e --- /dev/null +++ b/t/t3418-rebase-continue.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='git rebase --continue tests' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +set_fake_editor + +test_expect_success 'setup' ' + test_commit "commit-new-file-F1" F1 1 && + test_commit "commit-new-file-F2" F2 2 && + + git checkout -b topic HEAD^ && + test_commit "commit-new-file-F2-on-topic-branch" F2 22 && + + git checkout master +' + +test_expect_success 'interactive rebase --continue works with touched file' ' + rm -fr .git/rebase-* && + git reset --hard && + git checkout master && + + FAKE_LINES="edit 1" git rebase -i HEAD^ && + test-chmtime =-60 F1 && + git rebase --continue +' + +test_expect_success 'non-interactive rebase --continue works with touched file' ' + rm -fr .git/rebase-* && + git reset --hard && + git checkout master && + + test_must_fail git rebase --onto master master topic && + echo "Resolved" >F2 && + git add F2 && + test-chmtime =-60 F1 && + git rebase --continue +' + +test_done diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh index e51e505a9f..c10b28cf57 100755 --- a/t/t3505-cherry-pick-empty.sh +++ b/t/t3505-cherry-pick-empty.sh @@ -13,12 +13,30 @@ test_expect_success setup ' git checkout -b empty-branch && test_tick && - git commit --allow-empty -m "empty" + git commit --allow-empty -m "empty" && + + echo third >> file1 && + git add file1 && + test_tick && + git commit --allow-empty-message -m "" ' test_expect_success 'cherry-pick an empty commit' ' git checkout master && { + git cherry-pick empty-branch^ + test "$?" = 1 + } +' + +test_expect_success 'index lockfile was removed' ' + + test ! -f .git/index.lock + +' + +test_expect_success 'cherry-pick a commit with an empty message' ' + git checkout master && { git cherry-pick empty-branch test "$?" = 1 } diff --git a/t/t3700-add.sh b/t/t3700-add.sh index d03495dc7a..7d7140db38 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -272,17 +272,20 @@ test_expect_success 'git add --dry-run of non-existing file' " echo \"fatal: pathspec 'ignored-file' did not match any files\" | test_cmp - actual " -cat >expect <<EOF +cat >expect.err <<\EOF The following paths are ignored by one of your .gitignore files: ignored-file Use -f if you really want to add them. fatal: no files added +EOF +cat >expect.out <<\EOF add 'track-this' EOF test_expect_success 'git add --dry-run --ignore-missing of non-existing file' ' - test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual 2>&1 && - test_cmp expect actual + test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err ' test_done diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 5b10e976a3..61de8a2718 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -32,7 +32,7 @@ EOF sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java -builtin_patterns="bibtex cpp html java objc pascal php python ruby tex" +builtin_patterns="bibtex cpp csharp html java objc pascal php python ruby tex" for p in $builtin_patterns do test_expect_success "builtin $p pattern compiles" ' diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 1bd8e5ee3a..d99814ac64 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -114,6 +114,69 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' ! test -s actual4 ' +test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' ' + git config diff.ignoreSubmodules all && + git diff HEAD >actual && + ! test -s actual && + git config submodule.subname.ignore none && + git config submodule.subname.path sub && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config submodule.subname.ignore all && + git diff HEAD >actual2 && + ! test -s actual2 && + git config submodule.subname.ignore untracked && + git diff HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git config submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git diff HEAD --ignore-submodules=none >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname && + git config --unset diff.ignoreSubmodules +' + +test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' ' + git config diff.ignoreSubmodules dirty && + git diff HEAD >actual && + ! test -s actual && + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sub && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config -f .gitmodules submodule.subname.ignore all && + git config -f .gitmodules submodule.subname.path sub && + git diff HEAD >actual2 && + ! test -s actual2 && + git config -f .gitmodules submodule.subname.ignore untracked && + git diff HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git config -f .gitmodules submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git config submodule.subname.ignore none && + git config submodule.subname.path sub && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname && + git config --remove-section -f .gitmodules submodule.subname && + git config --unset diff.ignoreSubmodules && + rm .gitmodules +' + test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' ( cd sub && @@ -146,6 +209,103 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' ! test -s actual4 ' +test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' ' + git config submodule.subname.ignore all && + git config submodule.subname.path sub && + git diff HEAD >actual2 && + ! test -s actual2 && + git config submodule.subname.ignore untracked && + git diff HEAD >actual3 && + ! test -s actual3 && + git config submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git diff --ignore-submodules=none HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname +' + +test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' ' + git config --add -f .gitmodules submodule.subname.ignore all && + git config --add -f .gitmodules submodule.subname.path sub && + git diff HEAD >actual2 && + ! test -s actual2 && + git config -f .gitmodules submodule.subname.ignore untracked && + git diff HEAD >actual3 && + ! test -s actual3 && + git config -f .gitmodules submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git config submodule.subname.ignore none && + git config submodule.subname.path sub && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname && + git config --remove-section -f .gitmodules submodule.subname && + rm .gitmodules +' + +test_expect_success 'git diff between submodule commits' ' + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git diff --ignore-submodules=dirty HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git diff --ignore-submodules HEAD^..HEAD >actual && + ! test -s actual +' + +test_expect_success 'git diff between submodule commits [.git/config]' ' + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config submodule.subname.ignore dirty && + git config submodule.subname.path sub && + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config submodule.subname.ignore all && + git diff HEAD^..HEAD >actual && + ! test -s actual && + git diff --ignore-submodules=dirty HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + git config --remove-section submodule.subname +' + +test_expect_success 'git diff between submodule commits [.gitmodules]' ' + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config --add -f .gitmodules submodule.subname.ignore dirty && + git config --add -f .gitmodules submodule.subname.path sub && + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config -f .gitmodules submodule.subname.ignore all && + git diff HEAD^..HEAD >actual && + ! test -s actual && + git config submodule.subname.ignore dirty && + git config submodule.subname.path sub && + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + git config --remove-section submodule.subname && + git config --remove-section -f .gitmodules submodule.subname && + rm .gitmodules +' + test_expect_success 'git diff (empty submodule dir)' ' : >empty && rm -rf sub/* sub/.git && diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh new file mode 100755 index 0000000000..8a3c63b9e2 --- /dev/null +++ b/t/t4045-diff-relative.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='diff --relative tests' +. ./test-lib.sh + +test_expect_success 'setup' ' + git commit --allow-empty -m empty && + echo content >file1 && + mkdir subdir && + echo other content >subdir/file2 && + git add . && + git commit -m one +' + +check_diff() { +expect=$1; shift +cat >expected <<EOF +diff --git a/$expect b/$expect +new file mode 100644 +index 0000000..25c05ef +--- /dev/null ++++ b/$expect +@@ -0,0 +1 @@ ++other content +EOF +test_expect_success "-p $*" " + git diff -p $* HEAD^ >actual && + test_cmp expected actual +" +} + +check_stat() { +expect=$1; shift +cat >expected <<EOF + $expect | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) +EOF +test_expect_success "--stat $*" " + git diff --stat $* HEAD^ >actual && + test_cmp expected actual +" +} + +check_raw() { +expect=$1; shift +cat >expected <<EOF +:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect +EOF +test_expect_success "--raw $*" " + git diff --no-abbrev --raw $* HEAD^ >actual && + test_cmp expected actual +" +} + +for type in diff stat raw; do + check_$type file2 --relative=subdir/ + check_$type file2 --relative=subdir + check_$type dir/file2 --relative=sub +done + +test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 810b04b817..1c3d8ed548 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -4,66 +4,71 @@ test_description='git am running' . ./test-lib.sh -cat >msg <<EOF -second - -Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy -eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam -voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita -kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem -ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod -tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At -vero eos et accusam et justo duo dolores et ea rebum. - - Duis autem vel eum iriure dolor in hendrerit in vulputate velit - esse molestie consequat, vel illum dolore eu feugiat nulla facilisis - at vero eros et accumsan et iusto odio dignissim qui blandit - praesent luptatum zzril delenit augue duis dolore te feugait nulla - facilisi. - - -Lorem ipsum dolor sit amet, -consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut -laoreet dolore magna aliquam erat volutpat. - - git - --- - +++ - -Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit -lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure -dolor in hendrerit in vulputate velit esse molestie consequat, vel illum -dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio -dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te -feugait nulla facilisi. -EOF - -cat >failmail <<EOF -From foo@example.com Fri May 23 10:43:49 2008 -From: foo@example.com -To: bar@example.com -Subject: Re: [RFC/PATCH] git-foo.sh -Date: Fri, 23 May 2008 05:23:42 +0200 - -Sometimes we have to find out that there's nothing left. - -EOF - -cat >pine <<EOF -From MAILER-DAEMON Fri May 23 10:43:49 2008 -Date: 23 May 2008 05:23:42 +0200 -From: Mail System Internal Data <MAILER-DAEMON@example.com> -Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA -Message-ID: <foo-0001@example.com> - -This text is part of the internal format of your mail folder, and is not -a real message. It is created automatically by the mail system software. -If deleted, important folder data will be lost, and it will be re-created -with the data reset to initial values. - -EOF - -echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected +test_expect_success 'setup: messages' ' + cat >msg <<-\EOF && + second + + Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita + kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem + ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod + tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At + vero eos et accusam et justo duo dolores et ea rebum. + + EOF + q_to_tab <<-\EOF >>msg && + QDuis autem vel eum iriure dolor in hendrerit in vulputate velit + Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis + Qat vero eros et accumsan et iusto odio dignissim qui blandit + Qpraesent luptatum zzril delenit augue duis dolore te feugait nulla + Qfacilisi. + EOF + cat >>msg <<-\EOF && + + Lorem ipsum dolor sit amet, + consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut + laoreet dolore magna aliquam erat volutpat. + + git + --- + +++ + + Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure + dolor in hendrerit in vulputate velit esse molestie consequat, vel illum + dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio + dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te + feugait nulla facilisi. + EOF + + cat >failmail <<-\EOF && + From foo@example.com Fri May 23 10:43:49 2008 + From: foo@example.com + To: bar@example.com + Subject: Re: [RFC/PATCH] git-foo.sh + Date: Fri, 23 May 2008 05:23:42 +0200 + + Sometimes we have to find out that there'\''s nothing left. + + EOF + + cat >pine <<-\EOF && + From MAILER-DAEMON Fri May 23 10:43:49 2008 + Date: 23 May 2008 05:23:42 +0200 + From: Mail System Internal Data <MAILER-DAEMON@example.com> + Subject: DON'\''T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA + Message-ID: <foo-0001@example.com> + + This text is part of the internal format of your mail folder, and is not + a real message. It is created automatically by the mail system software. + If deleted, important folder data will be lost, and it will be re-created + with the data reset to initial values. + + EOF + + signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +' test_expect_success setup ' echo hello >file && @@ -71,11 +76,13 @@ test_expect_success setup ' test_tick && git commit -m first && git tag first && + echo world >>file && git add file && test_tick && git commit -s -F msg && git tag second && + git format-patch --stdout first >patch1 && { echo "X-Fake-Field: Line One" && @@ -89,74 +96,101 @@ test_expect_success setup ' echo "X-Fake-Field: Line Three" && git format-patch --stdout first | sed -e "1d" } | append_cr >patch1-crlf.eml && + sed -n -e "3,\$p" msg >file && git add file && test_tick && git commit -m third && + git format-patch --stdout first >patch2 && + git checkout -b lorem && sed -n -e "11,\$p" msg >file && head -n 9 msg >>file && test_tick && git commit -a -m "moved stuff" && + echo goodbye >another && git add another && test_tick && git commit -m "added another file" && - git format-patch --stdout master >lorem-move.patch -' -# reset time -unset test_tick -test_tick + git format-patch --stdout master >lorem-move.patch && + + git checkout -b rename && + git mv file renamed && + git commit -m "renamed a file" && + + git format-patch -M --stdout lorem >rename.patch && + + git reset --soft lorem^ && + git commit -m "renamed a file and added another" && + + git format-patch -M --stdout lorem^ >rename-add.patch && + + # reset time + unset test_tick && + test_tick +' test_expect_success 'am applies patch correctly' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && git am <patch1 && ! test -d .git/rebase-apply && - test -z "$(git diff second)" && + git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' test_expect_success 'am applies patch e-mail not in a mbox' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && git am patch1.eml && ! test -d .git/rebase-apply && - test -z "$(git diff second)" && + git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' test_expect_success 'am applies patch e-mail not in a mbox with CRLF' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && git am patch1-crlf.eml && ! test -d .git/rebase-apply && - test -z "$(git diff second)" && + git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' -GIT_AUTHOR_NAME="Another Thor" -GIT_AUTHOR_EMAIL="a.thor@example.com" -GIT_COMMITTER_NAME="Co M Miter" -GIT_COMMITTER_EMAIL="c.miter@example.com" -export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL +test_expect_success 'setup: new author and committer' ' + GIT_AUTHOR_NAME="Another Thor" && + GIT_AUTHOR_EMAIL="a.thor@example.com" && + GIT_COMMITTER_NAME="Co M Miter" && + GIT_COMMITTER_EMAIL="c.miter@example.com" && + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL +' compare () { - test "$(git cat-file commit "$2" | grep "^$1 ")" = \ - "$(git cat-file commit "$3" | grep "^$1 ")" + a=$(git cat-file commit "$2" | grep "^$1 ") && + b=$(git cat-file commit "$3" | grep "^$1 ") && + test "$a" = "$b" } test_expect_success 'am changes committer and keeps author' ' test_tick && + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && git am patch2 && ! test -d .git/rebase-apply && test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" && - test -z "$(git diff master..HEAD)" && - test -z "$(git diff master^..HEAD^)" && + git diff --exit-code master..HEAD && + git diff --exit-code master^..HEAD^ && compare author master HEAD && compare author master^ HEAD^ && test "$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \ @@ -164,41 +198,55 @@ test_expect_success 'am changes committer and keeps author' ' ' test_expect_success 'am --signoff adds Signed-off-by: line' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout -b master2 first && git am --signoff <patch2 && + printf "%s\n" "$signoff" >expected && echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected && git cat-file commit HEAD^ | grep "Signed-off-by:" >actual && - test_cmp actual expected && + test_cmp expected actual && echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected && git cat-file commit HEAD | grep "Signed-off-by:" >actual && - test_cmp actual expected + test_cmp expected actual ' test_expect_success 'am stays in branch' ' - test "refs/heads/master2" = "$(git symbolic-ref HEAD)" + echo refs/heads/master2 >expected && + git symbolic-ref HEAD >actual && + test_cmp expected actual ' test_expect_success 'am --signoff does not add Signed-off-by: line if already there' ' git format-patch --stdout HEAD^ >patch3 && sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 + rm -fr .git/rebase-apply && + git reset --hard && git checkout HEAD^ && git am --signoff patch4 && - test "$(git cat-file commit HEAD | grep -c "^Signed-off-by:")" -eq 1 + git cat-file commit HEAD >actual && + test $(grep -c "^Signed-off-by:" actual) -eq 1 ' test_expect_success 'am without --keep removes Re: and [PATCH] stuff' ' - test "$(git rev-parse HEAD)" = "$(git rev-parse master2)" + git rev-parse HEAD >expected && + git rev-parse master2 >actual && + test_cmp expected actual ' test_expect_success 'am --keep really keeps the subject' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout HEAD^ && git am --keep patch4 && ! test -d .git/rebase-apply && - git cat-file commit HEAD | - fgrep "Re: Re: Re: [PATCH 1/5 v2] third" + git cat-file commit HEAD >actual && + grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual ' test_expect_success 'am -3 falls back to 3-way merge' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout -b lorem2 master2 && sed -n -e "3,\$p" msg >file && head -n 9 msg >>file && @@ -207,34 +255,75 @@ test_expect_success 'am -3 falls back to 3-way merge' ' git commit -m "copied stuff" && git am -3 lorem-move.patch && ! test -d .git/rebase-apply && - test -z "$(git diff lorem)" + git diff --exit-code lorem +' + +test_expect_success 'am can rename a file' ' + grep "^rename from" rename.patch && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout lorem^0 && + git am rename.patch && + ! test -d .git/rebase-apply && + git update-index --refresh && + git diff --exit-code rename +' + +test_expect_success 'am -3 can rename a file' ' + grep "^rename from" rename.patch && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout lorem^0 && + git am -3 rename.patch && + ! test -d .git/rebase-apply && + git update-index --refresh && + git diff --exit-code rename +' + +test_expect_success 'am -3 can rename a file after falling back to 3-way merge' ' + grep "^rename from" rename-add.patch && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout lorem^0 && + git am -3 rename-add.patch && + ! test -d .git/rebase-apply && + git update-index --refresh && + git diff --exit-code rename ' test_expect_success 'am -3 -q is quiet' ' + rm -fr .git/rebase-apply && + git checkout -f lorem2 && git reset master2 --hard && sed -n -e "3,\$p" msg >file && head -n 9 msg >>file && git add file && test_tick && git commit -m "copied stuff" && - git am -3 -q lorem-move.patch > output.out 2>&1 && + git am -3 -q lorem-move.patch >output.out 2>&1 && ! test -s output.out ' test_expect_success 'am pauses on conflict' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout lorem2^^ && test_must_fail git am lorem-move.patch && test -d .git/rebase-apply ' test_expect_success 'am --skip works' ' + echo goodbye >expected && git am --skip && ! test -d .git/rebase-apply && - test -z "$(git diff lorem2^^ -- file)" && - test goodbye = "$(cat another)" + git diff --exit-code lorem2^^ -- file && + test_cmp expected another ' test_expect_success 'am --resolved works' ' + echo goodbye >expected && + rm -fr .git/rebase-apply && + git reset --hard && git checkout lorem2^^ && test_must_fail git am lorem-move.patch && test -d .git/rebase-apply && @@ -242,22 +331,29 @@ test_expect_success 'am --resolved works' ' git add file && git am --resolved && ! test -d .git/rebase-apply && - test goodbye = "$(cat another)" + test_cmp expected another ' test_expect_success 'am takes patches from a Pine mailbox' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && cat pine patch1 | git am && ! test -d .git/rebase-apply && - test -z "$(git diff master^..HEAD)" + git diff --exit-code master^..HEAD ' test_expect_success 'am fails on mail without patch' ' + rm -fr .git/rebase-apply && + git reset --hard && test_must_fail git am <failmail && - rm -r .git/rebase-apply/ + git am --abort && + ! test -d .git/rebase-apply ' test_expect_success 'am fails on empty patch' ' + rm -fr .git/rebase-apply && + git reset --hard && echo "---" >>failmail && test_must_fail git am <failmail && git am --skip && @@ -266,28 +362,34 @@ test_expect_success 'am fails on empty patch' ' test_expect_success 'am works from stdin in subdirectory' ' rm -fr subdir && + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && ( mkdir -p subdir && cd subdir && git am <../patch1 ) && - test -z "$(git diff second)" + git diff --exit-code second ' test_expect_success 'am works from file (relative path given) in subdirectory' ' rm -fr subdir && + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && ( mkdir -p subdir && cd subdir && git am ../patch1 ) && - test -z "$(git diff second)" + git diff --exit-code second ' test_expect_success 'am works from file (absolute path given) in subdirectory' ' rm -fr subdir && + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && P=$(pwd) && ( @@ -295,27 +397,31 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' ' cd subdir && git am "$P/patch1" ) && - test -z "$(git diff second)" + git diff --exit-code second ' test_expect_success 'am --committer-date-is-author-date' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && git am --committer-date-is-author-date patch1 && git cat-file commit HEAD | sed -e "/^\$/q" >head1 && - at=$(sed -ne "/^author /s/.*> //p" head1) && - ct=$(sed -ne "/^committer /s/.*> //p" head1) && - test "$at" = "$ct" + sed -ne "/^author /s/.*> //p" head1 >at && + sed -ne "/^committer /s/.*> //p" head1 >ct && + test_cmp at ct ' test_expect_success 'am without --committer-date-is-author-date' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && git am patch1 && git cat-file commit HEAD | sed -e "/^\$/q" >head1 && - at=$(sed -ne "/^author /s/.*> //p" head1) && - ct=$(sed -ne "/^committer /s/.*> //p" head1) && - test "$at" != "$ct" + sed -ne "/^author /s/.*> //p" head1 >at && + sed -ne "/^committer /s/.*> //p" head1 >ct && + ! test_cmp at ct ' # This checks for +0000 because TZ is set to UTC and that should @@ -323,41 +429,51 @@ test_expect_success 'am without --committer-date-is-author-date' ' # by test_tick that uses -0700 timezone; if this feature does not # work, we will see that instead of +0000. test_expect_success 'am --ignore-date' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && git am --ignore-date patch1 && git cat-file commit HEAD | sed -e "/^\$/q" >head1 && - at=$(sed -ne "/^author /s/.*> //p" head1) && - echo "$at" | grep "+0000" + sed -ne "/^author /s/.*> //p" head1 >at && + grep "+0000" at ' test_expect_success 'am into an unborn branch' ' + git rev-parse first^{tree} >expected && + rm -fr .git/rebase-apply && + git reset --hard && rm -fr subdir && - mkdir -p subdir && + mkdir subdir && git format-patch --numbered-files -o subdir -1 first && ( cd subdir && git init && git am 1 ) && - result=$( - cd subdir && git rev-parse HEAD^{tree} + ( + cd subdir && + git rev-parse HEAD^{tree} >../actual ) && - test "z$result" = "z$(git rev-parse first^{tree})" + test_cmp expected actual ' test_expect_success 'am newline in subject' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && - sed -e "s/second/second \\\n foo/" patch1 > patchnl && - git am < patchnl > output.out 2>&1 && + sed -e "s/second/second \\\n foo/" patch1 >patchnl && + git am <patchnl >output.out 2>&1 && grep "^Applying: second \\\n foo$" output.out ' test_expect_success 'am -q is quiet' ' + rm -fr .git/rebase-apply && + git reset --hard && git checkout first && test_tick && - git am -q < patch1 > output.out 2>&1 && + git am -q <patch1 >output.out 2>&1 && ! test -s output.out ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 95ac3f8cc8..2e51356947 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -441,5 +441,17 @@ test_expect_success 'log.decorate configuration' ' ' -test_done +test_expect_success 'show added path under "--follow -M"' ' + # This tests for a regression introduced in v1.7.2-rc0~103^2~2 + test_create_repo regression && + ( + cd regression && + test_commit needs-another-commit && + test_commit foo.bar && + git log -M --follow -p foo.bar.t && + git log -M --follow --stat foo.bar.t && + git log -M --follow --name-only foo.bar.t + ) +' +test_done diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh index 426b319bd3..02d4d2284d 100755 --- a/t/t5001-archive-attr.sh +++ b/t/t5001-archive-attr.sh @@ -4,7 +4,7 @@ test_description='git archive attribute tests' . ./test-lib.sh -SUBSTFORMAT=%H%n +SUBSTFORMAT='%H (%h)%n' test_expect_exists() { test_expect_success " $1 exists" "test -e $1" diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 319e389ed0..0b489f5b12 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -4,6 +4,11 @@ test_description='pulling into void' . ./test-lib.sh +modify () { + sed -e "$1" <"$2" >"$2.x" && + mv "$2.x" "$2" +} + D=`pwd` test_expect_success setup ' @@ -160,4 +165,61 @@ test_expect_success 'pull --rebase works on branch yet to be born' ' test_cmp expect actual ' +test_expect_success 'setup for detecting upstreamed changes' ' + mkdir src && + (cd src && + git init && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff && + git add stuff && + git commit -m "Initial revision" + ) && + git clone src dst && + (cd src && + modify s/5/43/ stuff && + git commit -a -m "5->43" && + modify s/6/42/ stuff && + git commit -a -m "Make it bigger" + ) && + (cd dst && + modify s/5/43/ stuff && + git commit -a -m "Independent discovery of 5->43" + ) +' + +test_expect_success 'git pull --rebase detects upstreamed changes' ' + (cd dst && + git pull --rebase && + test -z "$(git ls-files -u)" + ) +' + +test_expect_success 'setup for avoiding reapplying old patches' ' + (cd dst && + test_might_fail git rebase --abort && + git reset --hard origin/master + ) && + git clone --bare src src-replace.git && + rm -rf src && + mv src-replace.git src && + (cd dst && + modify s/2/22/ stuff && + git commit -a -m "Change 2" && + modify s/3/33/ stuff && + git commit -a -m "Change 3" && + modify s/4/44/ stuff && + git commit -a -m "Change 4" && + git push && + + modify s/44/55/ stuff && + git commit --amend -a -m "Modified Change 4" + ) +' + +test_expect_success 'git pull --rebase does not reapply old patches' ' + (cd dst && + test_must_fail git pull --rebase && + test 1 = $(find .git/rebase-apply -name "000*" | wc -l) + ) +' + test_done diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index 044603c26e..6b2a5f4a65 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -60,6 +60,15 @@ test_expect_success 'upload-pack fails due to error in rev-list' ' grep "bad tree object" output.err ' +test_expect_success 'upload-pack error message when bad ref requested' ' + + printf "0045want %s multi_ack_detailed\n00000009done\n0000" \ + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input && + test_must_fail git upload-pack . <input >output 2>output.err && + grep -q "not our ref" output.err && + ! grep -q multi_ack_detailed output.err +' + test_expect_success 'upload-pack fails due to error in pack-objects enumeration' ' printf "0032want %s\n00000009done\n0000" \ diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh index 8ab3d61f44..2cf42c73f1 100755 --- a/t/t6037-merge-ours-theirs.sh +++ b/t/t6037-merge-ours-theirs.sh @@ -58,7 +58,7 @@ test_expect_success 'pull with -X' ' git reset --hard master && git pull -s recursive -X ours . side && git reset --hard master && git pull -s recursive -Xtheirs . side && git reset --hard master && git pull -s recursive -X theirs . side && - git reset --hard master && ! git pull -s recursive -X bork . side + git reset --hard master && test_must_fail git pull -s recursive -X bork . side ' test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 203ffdb17a..4185b7ca1d 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -219,6 +219,12 @@ test_expect_success 'bisect and replacements' ' git bisect reset ' +test_expect_success 'index-pack and replacements' ' + git --no-replace-objects rev-list --objects HEAD | + git --no-replace-objects pack-objects test- && + git index-pack test-*.pack +' + # # test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 0da13a8d6b..2c55801ee8 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -143,11 +143,12 @@ test_expect_success 'more setup' ' test_expect_success 'use index-filter to move into a subdirectory' ' git branch directorymoved && git filter-branch -f --index-filter \ - "git ls-files -s | sed \"s-\\t-&newsubdir/-\" | + "git ls-files -s | sed \"s- -&newsubdir/-\" | GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \ git update-index --index-info && mv \"\$GIT_INDEX_FILE.new\" \"\$GIT_INDEX_FILE\"" directorymoved && - test -z "$(git diff HEAD directorymoved:newsubdir)"' + git diff --exit-code HEAD directorymoved:newsubdir +' test_expect_success 'stops when msg filter fails' ' old=$(git rev-parse HEAD) && diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 7d8ed68bef..3a43571cab 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -438,4 +438,20 @@ test_expect_success 'force removal of nested git work tree' ' ! test -d bar ' +test_expect_success 'git clean -e' ' + rm -fr repo && + mkdir repo && + ( + cd repo && + git init && + touch 1 2 3 known && + git add known && + git clean -f -e 1 -e 2 && + test -e 1 && + test -e 2 && + ! (test -e 3) && + test -e known + ) +' + test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 7538756487..bade2179b1 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -14,7 +14,7 @@ test_expect_success setup ' echo file > file && git add file && test_tick && - git commit -m upstream + git commit -m upstream && git clone . super && git clone super submodule && (cd super && @@ -42,7 +42,7 @@ test_expect_success 'change submodule url' ' ) && mv submodule moved-submodule && (cd super && - git config -f .gitmodules submodule.submodule.url ../moved-submodule + git config -f .gitmodules submodule.submodule.url ../moved-submodule && test_tick && git commit -a -m moved-submodule ) diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 4a7b8933f4..6ec559db0f 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -54,13 +54,129 @@ test_expect_success setup ' git merge -s ours a ' -test_expect_success 'merging with modify/modify conflict' ' +# History setup +# +# b +# / \ +# a d +# \ / +# c +# +# a in the main repository records to sub-a in the submodule and +# analogous b and c. d should be automatically found by merging c into +# b in the main repository. +test_expect_success 'setup for merge search' ' + mkdir merge-search && + cd merge-search && + git init && + mkdir sub && + (cd sub && + git init && + echo "file-a" > file-a && + git add file-a && + git commit -m "sub-a" && + git branch sub-a) && + git add sub && + git commit -m "a" && + git branch a && + + git checkout -b b && + (cd sub && + git checkout -b sub-b && + echo "file-b" > file-b && + git add file-b && + git commit -m "sub-b") && + git commit -a -m "b" && + + git checkout -b c a && + (cd sub && + git checkout -b sub-c sub-a && + echo "file-c" > file-c && + git add file-c && + git commit -m "sub-c") && + git commit -a -m "c" && + + git checkout -b d a && + (cd sub && + git checkout -b sub-d sub-b && + git merge sub-c) && + git commit -a -m "d" && + git branch test b && + cd .. +' + +test_expect_success 'merge with one side as a fast-forward of the other' ' + (cd merge-search && + git checkout -b test-forward b && + git merge d && + git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual && + (cd sub && + git rev-parse sub-d > ../expect) && + test_cmp actual expect) +' + +test_expect_success 'merging should conflict for non fast-forward' ' + (cd merge-search && + git checkout -b test-nonforward b && + (cd sub && + git rev-parse sub-d > ../expect) && + test_must_fail git merge c 2> actual && + grep $(cat expect) actual > /dev/null && + git reset --hard) +' + +test_expect_success 'merging should fail for ambiguous common parent' ' + cd merge-search && + git checkout -b test-ambiguous b && + (cd sub && + git checkout -b ambiguous sub-b && + git merge sub-c && + git rev-parse sub-d > ../expect1 && + git rev-parse ambiguous > ../expect2) && + test_must_fail git merge c 2> actual && + grep $(cat expect1) actual > /dev/null && + grep $(cat expect2) actual > /dev/null && + git reset --hard && + cd .. +' + +# in a situation like this +# +# submodule tree: +# +# sub-a --- sub-b --- sub-d +# +# main tree: +# +# e (sub-a) +# / +# bb (sub-b) +# \ +# f (sub-d) +# +# A merge between e and f should fail because one of the submodule +# commits (sub-a) does not descend from the submodule merge-base (sub-b). +# +test_expect_success 'merging should fail for changes that are backwards' ' + cd merge-search && + git checkout -b bb a && + (cd sub && + git checkout sub-b) && + git commit -a -m "bb" && + + git checkout -b e bb && + (cd sub && + git checkout sub-a) && + git commit -a -m "e" && + + git checkout -b f bb && + (cd sub && + git checkout sub-d) && + git commit -a -m "f" && - git checkout -b test1 a && - test_must_fail git merge b && - test -f .git/MERGE_MSG && - git diff && - test -n "$(git ls-files -u)" + git checkout -b test-backward e && + test_must_fail git merge f && + cd .. ' test_expect_success 'merging with a modify/modify conflict between merge bases' ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a72fe3ae64..9c14b853c0 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -808,24 +808,38 @@ test_expect_success POSIXPERM 'status succeeds in a read-only repository' ' (exit $status) ' +(cd sm && echo > bar && git add bar && git commit -q -m 'Add bar' && cd .. && git add sm) +new_head=$(cd sm && git rev-parse --short=7 --verify HEAD) +touch .gitmodules + cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: dir1/modified # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Untracked files: # (use "git add <file>..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' @@ -834,19 +848,89 @@ test_expect_success '--ignore-submodules=untracked suppresses submodules with un test_cmp expect output ' +test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' ' + git config diff.ignoreSubmodules dirty && + git status >output && + test_cmp expect output && + git config --add -f .gitmodules submodule.subname.ignore untracked && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname && + git config --unset diff.ignoreSubmodules +' + +test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config --remove-section -f .gitmodules submodule.subname +' + test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' ' + git config diff.ignoreSubmodules dirty && + git status >output && + ! test -s actual && + git config --add -f .gitmodules submodule.subname.ignore dirty && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname && + git config --unset diff.ignoreSubmodules +' + +test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' + test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' ' echo modified > sm/foo && git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' ' + git config --add -f .gitmodules submodule.subname.ignore dirty && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname +' + +test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' + cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) @@ -855,16 +939,21 @@ cat > expect << EOF # modified: dir1/modified # modified: sm (modified content) # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Untracked files: # (use "git add <file>..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' @@ -872,10 +961,34 @@ test_expect_success "--ignore-submodules=untracked doesn't suppress submodules w test_cmp expect output ' +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" ' + git config --add -f .gitmodules submodule.subname.ignore untracked && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname +' + +test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' + head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD) cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) @@ -883,21 +996,26 @@ cat > expect << EOF # modified: dir1/modified # modified: sm (new commits) # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Submodules changed but not updated: # -# * sm $head...$head2 (1): +# * sm $new_head...$head2 (1): # > 2nd commit # # Untracked files: # (use "git add <file>..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' @@ -905,10 +1023,47 @@ test_expect_success "--ignore-submodules=untracked doesn't suppress submodule su test_cmp expect output ' +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" ' + git config --add -f .gitmodules submodule.subname.ignore untracked && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname +' + +test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' + test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" ' + git config --add -f .gitmodules submodule.subname.ignore dirty && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname +' + +test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' cat > expect << EOF # On branch master @@ -921,6 +1076,7 @@ cat > expect << EOF # Untracked files: # (use "git add <file>..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked @@ -935,4 +1091,23 @@ test_expect_success "--ignore-submodules=all suppresses submodule summary" ' test_cmp expect output ' +test_expect_failure '.gitmodules ignore=all suppresses submodule summary' ' + git config --add -f .gitmodules submodule.subname.ignore all && + git config --add -f .gitmodules submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config -f .gitmodules --remove-section submodule.subname +' + +test_expect_failure '.git/config ignore=all suppresses submodule summary' ' + git config --add -f .gitmodules submodule.subname.ignore none && + git config --add -f .gitmodules submodule.subname.path sm && + git config --add submodule.subname.ignore all && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname && + git config -f .gitmodules --remove-section submodule.subname +' + test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index 3ea33db6c7..643ab03f99 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -111,7 +111,7 @@ test_expect_success '--amend option with empty author' ' test_when_finished "git checkout Initial" && echo "Empty author test" >>foo && test_tick && - ! git commit -a -m "empty author" --amend 2>err && + test_must_fail git commit -a -m "empty author" --amend 2>err && grep "empty ident" err ' @@ -125,7 +125,7 @@ test_expect_success '--amend option with missing author' ' test_when_finished "git checkout Initial" && echo "Missing author test" >>foo && test_tick && - ! git commit -a -m "malformed author" --amend 2>err && + test_must_fail git commit -a -m "malformed author" --amend 2>err && grep "empty ident" err ' diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 49f4e1599a..d82349a6a8 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -31,7 +31,7 @@ test_expect_success 'setup' ' test_expect_success 'will not overwrite untracked file' ' git reset --hard c1 && cat important > c2.c && - ! git merge c2 && + test_must_fail git merge c2 && test_cmp important c2.c ' @@ -39,7 +39,7 @@ test_expect_success 'will not overwrite new file' ' git reset --hard c1 && cat important > c2.c && git add c2.c && - ! git merge c2 && + test_must_fail git merge c2 && test_cmp important c2.c ' @@ -48,7 +48,7 @@ test_expect_success 'will not overwrite staged changes' ' cat important > c2.c && git add c2.c && rm c2.c && - ! git merge c2 && + test_must_fail git merge c2 && git checkout c2.c && test_cmp important c2.c ' @@ -58,7 +58,7 @@ test_expect_success 'will not overwrite removed file' ' git rm c1.c && git commit -m "rm c1.c" && cat important > c1.c && - ! git merge c1a && + test_must_fail git merge c1a && test_cmp important c1.c ' @@ -68,7 +68,7 @@ test_expect_success 'will not overwrite re-added file' ' git commit -m "rm c1.c" && cat important > c1.c && git add c1.c && - ! git merge c1a && + test_must_fail git merge c1a && test_cmp important c1.c ' @@ -79,7 +79,7 @@ test_expect_success 'will not overwrite removed file with staged changes' ' cat important > c1.c && git add c1.c && rm c1.c && - ! git merge c1a && + test_must_fail git merge c1a && git checkout c1.c && test_cmp important c1.c ' diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh new file mode 100755 index 0000000000..1a109b41dc --- /dev/null +++ b/t/t7609-merge-co-error-msgs.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +test_description='unpack-trees error messages' + +. ./test-lib.sh + + +test_expect_success 'setup' ' + echo one >one && + git add one && + git commit -a -m First && + + git checkout -b branch && + echo two >two && + echo three >three && + echo four >four && + echo five >five && + git add two three four five && + git commit -m Second && + + git checkout master && + echo other >two && + echo other >three && + echo other >four && + echo other >five +' + +cat >expect <<\EOF +error: The following untracked working tree files would be overwritten by merge: + two + three + four + five +Please move or remove them before you can merge. +EOF + +test_expect_success 'untracked files overwritten by merge' ' + test_must_fail git merge branch 2>out && + test_cmp out expect +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by merge: + two + three + four +Please, commit your changes or stash them before you can merge. +error: The following untracked working tree files would be overwritten by merge: + five +Please move or remove them before you can merge. +EOF + +test_expect_success 'untracked files or local changes ovewritten by merge' ' + git add two && + git add three && + git add four && + test_must_fail git merge branch 2>out && + test_cmp out expect +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by checkout: + rep/two + rep/one +Please, commit your changes or stash them before you can switch branches. +EOF + +test_expect_success 'cannot switch branches because of local changes' ' + git add five && + mkdir rep && + echo one >rep/one && + echo two >rep/two && + git add rep/one rep/two && + git commit -m Fourth && + git checkout master && + echo uno >rep/one && + echo dos >rep/two && + test_must_fail git checkout branch 2>out && + test_cmp out expect +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by checkout: + rep/two + rep/one +Please, commit your changes or stash them before you can switch branches. +EOF + +test_expect_success 'not uptodate file porcelain checkout error' ' + git add rep/one rep/two && + test_must_fail git checkout branch 2>out && + test_cmp out expect +' + +cat >expect <<\EOF +error: Updating the following directories would lose untracked files in it: + rep2 + rep + +EOF + +test_expect_success 'not_uptodate_dir porcelain checkout error' ' + git init uptodate && + cd uptodate && + mkdir rep && + mkdir rep2 && + touch rep/foo && + touch rep2/foo && + git add rep/foo rep2/foo && + git commit -m init && + git checkout -b branch && + git rm rep -r && + git rm rep2 -r && + >rep && + >rep2 && + git add rep rep2&& + git commit -m "added test as a file" && + git checkout master && + >rep/untracked-file && + >rep2/untracked-file && + test_must_fail git checkout branch 2>out && + test_cmp out ../expect +' + +test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index e768c3eb2d..f5a7bf47e9 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -14,6 +14,7 @@ Testing basic merge tool invocation' # running mergetool test_expect_success 'setup' ' + git config rerere.enabled true && echo master >file1 && mkdir subdir && echo master sub >subdir/file3 && @@ -71,19 +72,40 @@ test_expect_success 'mergetool in subdir' ' cd subdir && ( test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && - test "$(cat file3)" = "master new sub" ) + test "$(cat file3)" = "master new sub") && + cd .. ' -# We can't merge files from parent directories when running mergetool -# from a subdir. Is this a bug? -# -#test_expect_failure 'mergetool in subdir' ' -# cd subdir && ( -# ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && -# ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && -# test "$(cat ../file1)" = "master updated" && -# test "$(cat ../file2)" = "master new" && -# git commit -m "branch1 resolved with mergetool - subdir" ) -#' +test_expect_success 'mergetool on file in parent dir' ' + cd subdir && ( + ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + git commit -m "branch1 resolved with mergetool - subdir") && + cd .. +' + +test_expect_success 'mergetool skips autoresolved' ' + git checkout -b test4 branch1 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git reset --hard +' + +test_expect_success 'mergetool merges all from subdir' ' + cd subdir && ( + git config rerere.enabled false && + test_must_fail git merge master && + git mergetool --no-prompt && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + git add ../file1 ../file2 file3 && + git commit -m "branch2 resolved by mergetool from subdir") && + cd .. +' test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 8a6322765c..023f225a4b 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -65,7 +65,7 @@ do test_expect_success "grep -w $L (w)" ' : >expected && - ! git grep -n -w -e "^w" >actual && + test_must_fail git grep -n -w -e "^w" >actual && test_cmp expected actual ' diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 13766ab160..d5adae640b 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -271,6 +271,17 @@ test_expect_success 'able to dcommit to a subdirectory' " test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" " +test_expect_success 'dcommit should not fail with a touched file' ' + test_commit "commit-new-file-foo2" foo2 && + test-chmtime =-60 foo && + git svn dcommit +' + +test_expect_success 'rebase should not fail with a touched file' ' + test-chmtime =-60 foo && + git svn rebase +' + test_expect_success 'able to set-tree to a subdirectory' " echo cba > d && git update-index d && diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index 134411e0a5..3c4f31925f 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -20,7 +20,7 @@ test_expect_success 'setup svnrepo' ' ' test_expect_success 'start import with incomplete authors file' ' - ! git svn clone --authors-file=svn-authors "$svnrepo" x + test_must_fail git svn clone --authors-file=svn-authors "$svnrepo" x ' test_expect_success 'imported 2 revisions successfully' ' @@ -63,7 +63,7 @@ test_expect_success 'authors-file against globs' ' ' test_expect_success 'fetch fails on ee' ' - ( cd aa-work && ! git svn fetch --authors-file=../svn-authors ) + ( cd aa-work && test_must_fail git svn fetch --authors-file=../svn-authors ) ' tmp_config_get () { diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh index f337959ccc..22d80b0be2 100755 --- a/t/t9139-git-svn-non-utf8-commitencoding.sh +++ b/t/t9139-git-svn-non-utf8-commitencoding.sh @@ -39,7 +39,7 @@ do ( cd $H && git config --unset i18n.commitencoding && - ! git svn dcommit + test_must_fail git svn dcommit ) ' done diff --git a/t/t9140-git-svn-reset.sh b/t/t9140-git-svn-reset.sh index 0735526d4b..e855904629 100755 --- a/t/t9140-git-svn-reset.sh +++ b/t/t9140-git-svn-reset.sh @@ -41,7 +41,7 @@ test_expect_success 'modify hidden file in SVN repo' ' test_expect_success 'fetch fails on modified hidden file' ' ( cd g && git svn find-rev refs/remotes/git-svn > ../expect && - ! git svn fetch 2> ../errors && + test_must_fail git svn fetch 2> ../errors && git svn find-rev refs/remotes/git-svn > ../expect2 ) && fgrep "not found in commit" errors && test_cmp expect expect2 diff --git a/t/t9155-git-svn-fetch-deleted-tag.sh b/t/t9155-git-svn-fetch-deleted-tag.sh new file mode 100755 index 0000000000..a486a98f84 --- /dev/null +++ b/t/t9155-git-svn-fetch-deleted-tag.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='git svn fetch deleted tag' + +. ./lib-git-svn.sh + +test_expect_success 'setup svn repo' ' + mkdir -p import/trunk/subdir && + mkdir -p import/branches && + mkdir -p import/tags && + echo "base" >import/trunk/subdir/file && + svn_cmd import -m "import for git svn" import "$svnrepo" && + rm -rf import && + + svn_cmd mkdir -m "create mybranch directory" "$svnrepo/branches/mybranch" && + svn_cmd cp -m "create branch mybranch" "$svnrepo/trunk" "$svnrepo/branches/mybranch/trunk" && + + svn_cmd co "$svnrepo/trunk" svn_project && + (cd svn_project && + echo "trunk change" >>subdir/file && + svn_cmd ci -m "trunk change" subdir/file && + + svn_cmd switch "$svnrepo/branches/mybranch/trunk" && + echo "branch change" >>subdir/file && + svn_cmd ci -m "branch change" subdir/file + ) && + + svn_cmd cp -m "create mytag attempt 1" -r5 "$svnrepo/trunk/subdir" "$svnrepo/tags/mytag" && + svn_cmd rm -m "delete mytag attempt 1" "$svnrepo/tags/mytag" && + svn_cmd cp -m "create mytag attempt 2" -r5 "$svnrepo/branches/mybranch/trunk/subdir" "$svnrepo/tags/mytag" +' + +test_expect_success 'fetch deleted tags from same revision with checksum error' ' + git svn init --stdlayout "$svnrepo" git_project && + cd git_project && + git svn fetch && + + git diff --exit-code mybranch:trunk/subdir/file tags/mytag:file && + git diff --exit-code master:subdir/file tags/mytag^:file +' + +test_done diff --git a/t/t9156-git-svn-fetch-deleted-tag-2.sh b/t/t9156-git-svn-fetch-deleted-tag-2.sh new file mode 100755 index 0000000000..5ce7e2f3b0 --- /dev/null +++ b/t/t9156-git-svn-fetch-deleted-tag-2.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='git svn fetch deleted tag 2' + +. ./lib-git-svn.sh + +test_expect_success 'setup svn repo' ' + mkdir -p import/branches && + mkdir -p import/tags && + mkdir -p import/trunk/subdir1 && + mkdir -p import/trunk/subdir2 && + mkdir -p import/trunk/subdir3 && + echo "file1" >import/trunk/subdir1/file && + echo "file2" >import/trunk/subdir2/file && + echo "file3" >import/trunk/subdir3/file && + svn_cmd import -m "import for git svn" import "$svnrepo" && + rm -rf import && + + svn_cmd co "$svnrepo/trunk" svn_project && + (cd svn_project && + echo "change1" >>subdir1/file && + echo "change2" >>subdir2/file && + echo "change3" >>subdir3/file && + svn_cmd ci -m "change" . + ) && + + svn_cmd cp -m "create mytag 1" -r2 "$svnrepo/trunk/subdir1" "$svnrepo/tags/mytag" && + svn_cmd rm -m "delete mytag 1" "$svnrepo/tags/mytag" && + svn_cmd cp -m "create mytag 2" -r2 "$svnrepo/trunk/subdir2" "$svnrepo/tags/mytag" && + svn_cmd rm -m "delete mytag 2" "$svnrepo/tags/mytag" && + svn_cmd cp -m "create mytag 3" -r2 "$svnrepo/trunk/subdir3" "$svnrepo/tags/mytag" +' + +test_expect_success 'fetch deleted tags from same revision with no checksum error' ' + git svn init --stdlayout "$svnrepo" git_project && + cd git_project && + git svn fetch && + + git diff --exit-code master:subdir3/file tags/mytag:file && + git diff --exit-code master:subdir2/file tags/mytag^:file && + git diff --exit-code master:subdir1/file tags/mytag^^:file +' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 131f032988..96d07f1833 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -166,6 +166,63 @@ test_expect_success \ test `git rev-parse --verify master:file2` \ = `git rev-parse --verify verify--import-marks:copy-of-file2`' +test_tick +mt=$(git hash-object --stdin < /dev/null) +: >input.blob +: >marks.exp +: >tree.exp + +cat >input.commit <<EOF +commit refs/heads/verify--dump-marks +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +test the sparse array dumping routines with exponentially growing marks +COMMIT +EOF + +i=0 +l=4 +m=6 +n=7 +while test "$i" -lt 27; do + cat >>input.blob <<EOF +blob +mark :$l +data 0 +blob +mark :$m +data 0 +blob +mark :$n +data 0 +EOF + echo "M 100644 :$l l$i" >>input.commit + echo "M 100644 :$m m$i" >>input.commit + echo "M 100644 :$n n$i" >>input.commit + + echo ":$l $mt" >>marks.exp + echo ":$m $mt" >>marks.exp + echo ":$n $mt" >>marks.exp + + printf "100644 blob $mt\tl$i\n" >>tree.exp + printf "100644 blob $mt\tm$i\n" >>tree.exp + printf "100644 blob $mt\tn$i\n" >>tree.exp + + l=$(($l + $l)) + m=$(($m + $m)) + n=$(($l + $n)) + + i=$((1 + $i)) +done + +sort tree.exp > tree.exp_s + +test_expect_success 'A: export marks with large values' ' + cat input.blob input.commit | git fast-import --export-marks=marks.large && + git ls-tree refs/heads/verify--dump-marks >tree.out && + test_cmp tree.exp_s tree.out && + test_cmp marks.exp marks.large' + ### ### series B ### @@ -796,6 +853,60 @@ test_expect_success \ 'git fast-import <input && test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`' +test_expect_success \ + 'N: copy directory by id' \ + 'cat >expect <<-\EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf + EOF + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && + commit refs/heads/N4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy by tree hash + COMMIT + + from refs/heads/branch^0 + M 040000 $subdir file3 + INPUT_END + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4^ N4 >actual && + compare_diff_raw expect actual' + +test_expect_success \ + 'N: modify copied tree' \ + 'cat >expect <<-\EOF && + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf + EOF + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && + commit refs/heads/N5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy by tree hash + COMMIT + + from refs/heads/branch^0 + M 040000 $subdir file3 + + commit refs/heads/N5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + modify directory copy + COMMIT + + M 644 inline file3/file5 + data <<EOF + $file5_data + EOF + INPUT_END + git fast-import <input && + git diff-tree -C --find-copies-harder -r N5^^ N5 >actual && + compare_diff_raw expect actual' + ### ### series O ### diff --git a/t/test-lib.sh b/t/test-lib.sh index e5523dd690..29fd7209cf 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -127,14 +127,13 @@ do -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) - quiet=t; shift ;; + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; --with-dashes) with_dashes=t; shift ;; --no-color) color=; shift ;; - --no-python) - # noop now... - shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) valgrind=t; verbose=t; shift ;; --tee) @@ -257,6 +256,10 @@ q_to_cr () { tr Q '\015' } +q_to_tab () { + tr Q '\011' +} + append_cr () { sed -e 's/$/Q/' | tr Q '\015' } diff --git a/transport-helper.c b/transport-helper.c index 191fbf798a..acfc88e3f1 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -689,7 +689,7 @@ static int push_refs_with_export(struct transport *transport, struct child_process *helper, exporter; struct helper_data *data = transport->data; char *export_marks = NULL, *import_marks = NULL; - struct string_list revlist_args = { NULL, 0, 0 }; + struct string_list revlist_args = STRING_LIST_INIT_NODUP; struct strbuf buf = STRBUF_INIT; helper = get_helper(transport); diff --git a/tree-diff.c b/tree-diff.c index 1fb3e94614..cd659c6fe4 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -359,6 +359,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_tree_release_paths(&diff_opts); /* Go through the new set of filepairing, and see if we find a more interesting one */ + opt->found_follow = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -376,6 +377,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_tree_release_paths(opt); opt->paths[0] = xstrdup(p->one->path); diff_tree_setup_paths(opt->paths, opt); + + /* + * The caller expects us to return a set of vanilla + * filepairs to let a later call to diffcore_std() + * it makes to sort the renames out (among other + * things), but we already have found renames + * ourselves; signal diffcore_std() not to muck with + * rename information. + */ + opt->found_follow = 1; break; } } @@ -412,7 +423,7 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha init_tree_desc(&t1, tree1, size1); init_tree_desc(&t2, tree2, size2); retval = diff_tree(&t1, &t2, base, opt); - if (DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) { + if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) { init_tree_desc(&t1, tree1, size1); init_tree_desc(&t2, tree2, size2); try_to_follow_renames(&t1, &t2, base, opt); diff --git a/tree-walk.c b/tree-walk.c index 67a9a0c5a5..a9bbf4e235 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -1,5 +1,6 @@ #include "cache.h" #include "tree-walk.h" +#include "unpack-trees.h" #include "tree.h" static const char *get_mode(const char *str, unsigned int *modep) @@ -310,6 +311,7 @@ static void free_extended_entry(struct tree_desc_x *t) int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { int ret = 0; + int error = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); int i; struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); @@ -377,8 +379,11 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) if (!mask) break; ret = info->fn(n, mask, dirmask, entry, info); - if (ret < 0) - break; + if (ret < 0) { + error = ret; + if (!info->show_all_errors) + break; + } mask &= ret; ret = 0; for (i = 0; i < n; i++) @@ -389,7 +394,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) for (i = 0; i < n; i++) free_extended_entry(tx + i); free(tx); - return ret; + return error; } static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) diff --git a/tree-walk.h b/tree-walk.h index 42110a465f..88ea7e9298 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -45,6 +45,7 @@ struct traverse_info { unsigned long conflicts; traverse_callback_t fn; void *data; + int show_all_errors; }; int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); diff --git a/unpack-trees.c b/unpack-trees.c index 8cf0da317d..3c7a7c9cde 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -13,37 +13,42 @@ * Error messages expected by scripts out of plumbing commands such as * read-tree. Non-scripted Porcelain is not required to use these messages * and in fact are encouraged to reword them to better suit their particular - * situation better. See how "git checkout" replaces not_uptodate_file to - * explain why it does not allow switching between branches when you have - * local changes, for example. + * situation better. See how "git checkout" and "git merge" replaces + * them using set_porcelain_error_msgs(), for example. */ -static struct unpack_trees_error_msgs unpack_plumbing_errors = { - /* would_overwrite */ +const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { + /* ERROR_WOULD_OVERWRITE */ "Entry '%s' would be overwritten by merge. Cannot merge.", - /* not_uptodate_file */ + /* ERROR_NOT_UPTODATE_FILE */ "Entry '%s' not uptodate. Cannot merge.", - /* not_uptodate_dir */ + /* ERROR_NOT_UPTODATE_DIR */ "Updating '%s' would lose untracked files in it", - /* would_lose_untracked */ - "Untracked working tree file '%s' would be %s by merge.", + /* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */ + "Untracked working tree file '%s' would be overwritten by merge.", - /* bind_overlap */ + /* ERROR_WOULD_LOSE_UNTRACKED_REMOVED */ + "Untracked working tree file '%s' would be removed by merge.", + + /* ERROR_BIND_OVERLAP */ "Entry '%s' overlaps with '%s'. Cannot bind.", - /* sparse_not_uptodate_file */ + /* ERROR_SPARSE_NOT_UPTODATE_FILE */ "Entry '%s' not uptodate. Cannot update sparse checkout.", - /* would_lose_orphaned */ - "Working tree file '%s' would be %s by sparse checkout update.", + /* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */ + "Working tree file '%s' would be overwritten by sparse checkout update.", + + /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */ + "Working tree file '%s' would be removed by sparse checkout update.", }; -#define ERRORMSG(o,fld) \ - ( ((o) && (o)->msgs.fld) \ - ? ((o)->msgs.fld) \ - : (unpack_plumbing_errors.fld) ) +#define ERRORMSG(o,type) \ + ( ((o) && (o)->msgs[(type)]) \ + ? ((o)->msgs[(type)]) \ + : (unpack_plumbing_errors[(type)]) ) static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, unsigned int set, unsigned int clear) @@ -53,6 +58,9 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, clear |= CE_HASHED | CE_UNHASHED; + if (set & CE_REMOVE) + set |= CE_WT_REMOVE; + memcpy(new, ce, size); new->next = NULL; new->ce_flags = (new->ce_flags & ~clear) | set; @@ -60,6 +68,73 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, } /* + * add error messages on path <path> + * corresponding to the type <e> with the message <msg> + * indicating if it should be display in porcelain or not + */ +static int add_rejected_path(struct unpack_trees_options *o, + enum unpack_trees_error_types e, + const char *path) +{ + struct rejected_paths_list *newentry; + int porcelain = o && (o)->msgs[e]; + /* + * simply display the given error message if in plumbing mode + */ + if (!porcelain) + o->show_all_errors = 0; + if (!o->show_all_errors) + return error(ERRORMSG(o, e), path); + + /* + * Otherwise, insert in a list for future display by + * display_error_msgs() + */ + newentry = xmalloc(sizeof(struct rejected_paths_list)); + newentry->path = (char *)path; + newentry->next = o->unpack_rejects[e]; + o->unpack_rejects[e] = newentry; + return -1; +} + +/* + * free all the structures allocated for the error <e> + */ +static void free_rejected_paths(struct unpack_trees_options *o, + enum unpack_trees_error_types e) +{ + while (o->unpack_rejects[e]) { + struct rejected_paths_list *del = o->unpack_rejects[e]; + o->unpack_rejects[e] = o->unpack_rejects[e]->next; + free(del); + } + free(o->unpack_rejects[e]); +} + +/* + * display all the error messages stored in a nice way + */ +static void display_error_msgs(struct unpack_trees_options *o) +{ + int e; + int something_displayed = 0; + for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) { + if (o->unpack_rejects[e]) { + struct rejected_paths_list *rp; + struct strbuf path = STRBUF_INIT; + something_displayed = 1; + for (rp = o->unpack_rejects[e]; rp; rp = rp->next) + strbuf_addf(&path, "\t%s\n", rp->path); + error(ERRORMSG(o, e), path.buf); + strbuf_release(&path); + free_rejected_paths(o, e); + } + } + if (something_displayed) + printf("Aborting\n"); +} + +/* * Unlink the last component and schedule the leading directories for * removal, such that empty directories get removed. */ @@ -84,7 +159,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE)) + if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) total++; } @@ -104,12 +179,6 @@ static int check_updates(struct unpack_trees_options *o) unlink_entry(ce); continue; } - - if (ce->ce_flags & CE_REMOVE) { - display_progress(progress, ++cnt); - if (o->update) - unlink_entry(ce); - } } remove_marked_cache_entries(&o->result); remove_scheduled_dirs(); @@ -132,15 +201,12 @@ static int check_updates(struct unpack_trees_options *o) } static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); -static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o); +static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o); static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) { const char *basename; - if (ce_stage(ce)) - return 0; - basename = strrchr(ce->name, '/'); basename = basename ? basename+1 : ce->name; return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; @@ -150,19 +216,36 @@ static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_opt { int was_skip_worktree = ce_skip_worktree(ce); - if (will_have_skip_worktree(ce, o)) + if (!ce_stage(ce) && will_have_skip_worktree(ce, o)) ce->ce_flags |= CE_SKIP_WORKTREE; else ce->ce_flags &= ~CE_SKIP_WORKTREE; /* - * We only care about files getting into the checkout area - * If merge strategies want to remove some, go ahead, this - * flag will be removed eventually in unpack_trees() if it's - * outside checkout area. + * if (!was_skip_worktree && !ce_skip_worktree()) { + * This is perfectly normal. Move on; + * } */ - if (ce->ce_flags & CE_REMOVE) - return 0; + + /* + * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout + * area as a result of ce_skip_worktree() shortcuts in + * verify_absent() and verify_uptodate(). + * Make sure they don't modify worktree if they are already + * outside checkout area + */ + if (was_skip_worktree && ce_skip_worktree(ce)) { + ce->ce_flags &= ~CE_UPDATE; + + /* + * By default, when CE_REMOVE is on, CE_WT_REMOVE is also + * on to get that file removed from both index and worktree. + * If that file is already outside worktree area, don't + * bother remove it. + */ + if (ce->ce_flags & CE_REMOVE) + ce->ce_flags &= ~CE_WT_REMOVE; + } if (!was_skip_worktree && ce_skip_worktree(ce)) { /* @@ -175,7 +258,7 @@ static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_opt ce->ce_flags |= CE_WT_REMOVE; } if (was_skip_worktree && !ce_skip_worktree(ce)) { - if (verify_absent_sparse(ce, "overwritten", o)) + if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) return -1; ce->ce_flags |= CE_UPDATE; } @@ -329,6 +412,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long { int i, ret, bottom; struct tree_desc t[MAX_UNPACK_TREES]; + void *buf[MAX_UNPACK_TREES]; struct traverse_info newinfo; struct name_entry *p; @@ -346,12 +430,16 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long const unsigned char *sha1 = NULL; if (dirmask & 1) sha1 = names[i].sha1; - fill_tree_descriptor(t+i, sha1); + buf[i] = fill_tree_descriptor(t+i, sha1); } bottom = switch_cache_bottom(&newinfo); ret = traverse_trees(n, t, &newinfo); restore_cache_bottom(&newinfo, bottom); + + for (i = 0; i < n; i++) + free(buf[i]); + return ret; } @@ -750,6 +838,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options setup_traverse_info(&info, prefix); info.fn = unpack_callback; info.data = o; + info.show_all_errors = o->show_all_errors; if (o->prefix) { /* @@ -798,14 +887,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ret = -1; goto done; } - /* - * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout - * area as a result of ce_skip_worktree() shortcuts in - * verify_absent() and verify_uptodate(). Clear them. - */ - if (ce_skip_worktree(ce)) - ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); - else + if (!ce_skip_worktree(ce)) empty_worktree = 0; } @@ -829,6 +911,8 @@ done: return ret; return_failed: + if (o->show_all_errors) + display_error_msgs(o); mark_all_ce_unused(o->src_index); ret = unpack_failed(o, NULL); goto done; @@ -838,7 +922,7 @@ return_failed: static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o) { - return error(ERRORMSG(o, would_overwrite), ce->name); + return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name); } static int same(struct cache_entry *a, struct cache_entry *b) @@ -860,7 +944,7 @@ static int same(struct cache_entry *a, struct cache_entry *b) */ static int verify_uptodate_1(struct cache_entry *ce, struct unpack_trees_options *o, - const char *error_msg) + enum unpack_trees_error_types error_type) { struct stat st; @@ -885,7 +969,7 @@ static int verify_uptodate_1(struct cache_entry *ce, if (errno == ENOENT) return 0; return o->gently ? -1 : - error(error_msg, ce->name); + add_rejected_path(o, error_type, ce->name); } static int verify_uptodate(struct cache_entry *ce, @@ -893,13 +977,13 @@ static int verify_uptodate(struct cache_entry *ce, { if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) return 0; - return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); + return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE); } static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o) { - return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file)); + return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE); } static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) @@ -915,13 +999,15 @@ static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_optio * Currently, git does not checkout subprojects during a superproject * checkout, so it is not going to overwrite anything. */ -static int verify_clean_submodule(struct cache_entry *ce, const char *action, +static int verify_clean_submodule(struct cache_entry *ce, + enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { return 0; } -static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, +static int verify_clean_subdirectory(struct cache_entry *ce, + enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { /* @@ -942,7 +1028,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, */ if (!hashcmp(sha1, ce->sha1)) return 0; - return verify_clean_submodule(ce, action, o); + return verify_clean_submodule(ce, error_type, o); } /* @@ -986,7 +1072,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, i = read_directory(&d, pathbuf, namelen+1, NULL); if (i) return o->gently ? -1 : - error(ERRORMSG(o, not_uptodate_dir), ce->name); + add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name); free(pathbuf); return cnt; } @@ -1011,9 +1097,9 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static int verify_absent_1(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o, - const char *error_msg) +static int verify_absent_1(struct cache_entry *ce, + enum unpack_trees_error_types error_type, + struct unpack_trees_options *o) { struct stat st; @@ -1051,7 +1137,7 @@ static int verify_absent_1(struct cache_entry *ce, const char *action, * files that are in "foo/" we would lose * them. */ - if (verify_clean_subdirectory(ce, action, o) < 0) + if (verify_clean_subdirectory(ce, error_type, o) < 0) return -1; return 0; } @@ -1068,22 +1154,28 @@ static int verify_absent_1(struct cache_entry *ce, const char *action, } return o->gently ? -1 : - error(ERRORMSG(o, would_lose_untracked), ce->name, action); + add_rejected_path(o, error_type, ce->name); } return 0; } -static int verify_absent(struct cache_entry *ce, const char *action, +static int verify_absent(struct cache_entry *ce, + enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) return 0; - return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); + return verify_absent_1(ce, error_type, o); } -static int verify_absent_sparse(struct cache_entry *ce, const char *action, +static int verify_absent_sparse(struct cache_entry *ce, + enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { - return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned)); + enum unpack_trees_error_types orphaned_error = error_type; + if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN) + orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN; + + return verify_absent_1(ce, orphaned_error, o); } static int merged_entry(struct cache_entry *merge, struct cache_entry *old, @@ -1092,8 +1184,10 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, int update = CE_UPDATE; if (!old) { - if (verify_absent(merge, "overwritten", o)) + if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) return -1; + if (!o->skip_sparse_checkout && will_have_skip_worktree(merge, o)) + update |= CE_SKIP_WORKTREE; invalidate_ce_path(merge, o); } else if (!(old->ce_flags & CE_CONFLICTED)) { /* @@ -1130,7 +1224,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, { /* Did it exist in the index? */ if (!old) { - if (verify_absent(ce, "removed", o)) + if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) return -1; return 0; } @@ -1279,7 +1373,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) if (index) return deleted_entry(index, index, o); if (ce && !head_deleted) { - if (verify_absent(ce, "removed", o)) + if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) return -1; } return 0; @@ -1412,7 +1506,7 @@ int bind_merge(struct cache_entry **src, o->merge_size); if (a && old) return o->gently ? -1 : - error(ERRORMSG(o, bind_overlap), a->name, old->name); + error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name); if (!a) return keep_entry(old, o); else diff --git a/unpack-trees.h b/unpack-trees.h index ef70eab390..6e049b0997 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -9,14 +9,22 @@ struct exclude_list; typedef int (*merge_fn_t)(struct cache_entry **src, struct unpack_trees_options *options); -struct unpack_trees_error_msgs { - const char *would_overwrite; - const char *not_uptodate_file; - const char *not_uptodate_dir; - const char *would_lose_untracked; - const char *bind_overlap; - const char *sparse_not_uptodate_file; - const char *would_lose_orphaned; +enum unpack_trees_error_types { + ERROR_WOULD_OVERWRITE = 0, + ERROR_NOT_UPTODATE_FILE, + ERROR_NOT_UPTODATE_DIR, + ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, + ERROR_WOULD_LOSE_UNTRACKED_REMOVED, + ERROR_BIND_OVERLAP, + ERROR_SPARSE_NOT_UPTODATE_FILE, + ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN, + ERROR_WOULD_LOSE_ORPHANED_REMOVED, + NB_UNPACK_TREES_ERROR_TYPES +}; + +struct rejected_paths_list { + char *path; + struct rejected_paths_list *next; }; struct unpack_trees_options { @@ -33,12 +41,18 @@ struct unpack_trees_options { diff_index_cached, debug_unpack, skip_sparse_checkout, - gently; + gently, + show_all_errors; const char *prefix; int cache_bottom; struct dir_struct *dir; merge_fn_t fn; - struct unpack_trees_error_msgs msgs; + const char *msgs[NB_UNPACK_TREES_ERROR_TYPES]; + /* + * Store error messages in an array, each case + * corresponding to a error message type + */ + struct rejected_paths_list *unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES]; int head_idx; int merge_size; diff --git a/upload-pack.c b/upload-pack.c index dc464d78b3..fc79ddef25 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -105,7 +105,7 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } -static int do_rev_list(int in, int out, void *create_full_pack) +static int do_rev_list(int in, int out, void *user_data) { int i; struct rev_info revs; @@ -118,23 +118,18 @@ static int do_rev_list(int in, int out, void *create_full_pack) if (use_thin_pack) revs.edge_hint = 1; - if (create_full_pack) { - const char *args[] = {"rev-list", "--all", NULL}; - setup_revisions(2, args, &revs, NULL); - } else { - for (i = 0; i < want_obj.nr; i++) { - struct object *o = want_obj.objects[i].item; - /* why??? */ - o->flags &= ~UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - for (i = 0; i < have_obj.nr; i++) { - struct object *o = have_obj.objects[i].item; - o->flags |= UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - setup_revisions(0, NULL, &revs, NULL); + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; + /* why??? */ + o->flags &= ~UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; + o->flags |= UNINTERESTING; + add_pending_object(&revs, o, NULL); } + setup_revisions(0, NULL, &revs, NULL); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); @@ -554,7 +549,8 @@ static void receive_needs(void) */ o = lookup_object(sha1_buf); if (!o || !(o->flags & OUR_REF)) - die("git upload-pack: not our ref %s", line+5); + die("git upload-pack: not our ref %s", + sha1_to_hex(sha1_buf)); if (!(o->flags & WANTED)) { o->flags |= WANTED; add_object_array(o, NULL, &want_obj); diff --git a/userdiff.c b/userdiff.c index c49cc1b67e..e5522159b3 100644 --- a/userdiff.c +++ b/userdiff.c @@ -82,6 +82,22 @@ PATTERNS("cpp", "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" "|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("csharp", + /* Keywords */ + "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" + /* Methods and constructors */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" + /* Properties */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" + /* Type definitions */ + "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" + /* Namespace */ + "^[ \t]*(namespace[ \t]+.*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" + "|[^[:space:]]|[\x80-\xff]+"), { "default", NULL, -1, { NULL, 0 } }, }; #undef PATTERNS @@ -40,7 +40,8 @@ void *xmalloc(size_t size) if (!ret && !size) ret = malloc(1); if (!ret) - die("Out of memory, malloc failed"); + die("Out of memory, malloc failed (tried to allocate %lu bytes)", + (unsigned long)size); } #ifdef XMALLOC_POISON memset(ret, 0xA5, size); diff --git a/wt-status.c b/wt-status.c index 2f9e33c8fa..54b6b03b9c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -313,8 +313,10 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); if (!s->show_untracked_files) DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); - if (s->ignore_submodule_arg) + if (s->ignore_submodule_arg) { + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; rev.prune_data = s->pathspec; @@ -331,8 +333,10 @@ static void wt_status_collect_changes_index(struct wt_status *s) opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; setup_revisions(0, NULL, &rev, &opt); - if (s->ignore_submodule_arg) + if (s->ignore_submodule_arg) { + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + } rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_updated_cb; |