diff options
135 files changed, 7244 insertions, 1120 deletions
diff --git a/Documentation/RelNotes/1.7.6.2.txt b/Documentation/RelNotes/1.7.6.2.txt new file mode 100644 index 0000000000..67ae414965 --- /dev/null +++ b/Documentation/RelNotes/1.7.6.2.txt @@ -0,0 +1,8 @@ +Git v1.7.6.2 Release Notes +========================== + +Fixes since v1.7.6.1 +-------------------- + + * v1.7.6.1 broke "git push --quiet"; it used to be a no-op against an old + version of Git running on the other end, but v1.7.6.1 made it abort. diff --git a/Documentation/RelNotes/1.7.6.3.txt b/Documentation/RelNotes/1.7.6.3.txt new file mode 100644 index 0000000000..95971831b9 --- /dev/null +++ b/Documentation/RelNotes/1.7.6.3.txt @@ -0,0 +1,24 @@ +Git v1.7.6.3 Release Notes +========================== + +Fixes since v1.7.6.2 +-------------------- + + * "git -c var=value subcmd" misparsed the custom configuration when + value contained an equal sign. + + * "git fetch" had a major performance regression, wasting many + needless cycles in a repository where there is no submodules + present. This was especially bad, when there were many refs. + + * "git reflog $refname" did not default to the "show" subcommand as + the documentation advertised the command to do. + + * "git reset" did not leave meaningful log message in the reflog. + + * "git status --ignored" did not show ignored items when there is no + untracked items. + + * "git tag --contains $commit" was unnecessarily inefficient. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.6.4.txt b/Documentation/RelNotes/1.7.6.4.txt new file mode 100644 index 0000000000..e19acac2da --- /dev/null +++ b/Documentation/RelNotes/1.7.6.4.txt @@ -0,0 +1,32 @@ +Git v1.7.6.4 Release Notes +========================== + +Fixes since v1.7.6.3 +-------------------- + + * The error reporting logic of "git am" when the command is fed a file + whose mail-storage format is unknown was fixed. + + * "git branch --set-upstream @{-1} foo" did not expand @{-1} correctly. + + * "git check-ref-format --print" used to parrot a candidate string that + began with a slash (e.g. /refs/heads/master) without stripping it, to make + the result a suitably normalized string the caller can append to "$GIT_DIR/". + + * "git clone" failed to clone locally from a ".git" file that itself + is not a directory but is a pointer to one. + + * "git clone" from a local repository that borrows from another + object store using a relative path in its objects/info/alternates + file did not adjust the alternates in the resulting repository. + + * "git describe --dirty" did not refresh the index before checking the + state of the working tree files. + + * "git ls-files ../$path" that is run from a subdirectory reported errors + incorrectly when there is no such path that matches the given pathspec. + + * "git mergetool" could loop forever prompting when nothing can be read + from the standard input. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt index dcbda84e86..7655cccfaa 100644 --- a/Documentation/RelNotes/1.7.7.txt +++ b/Documentation/RelNotes/1.7.7.txt @@ -8,7 +8,7 @@ Updates since v1.7.6 * Interix, Cygwin and Minix ports got updated. - * A handful of patches to update git-p4 (in contrib/). + * Various updates to git-p4 (in contrib/), fast-import, and git-svn. * Gitweb learned to read from /etc/gitweb-common.conf when it exists, before reading from gitweb_config.perl or from /etc/gitweb.conf @@ -20,69 +20,96 @@ Updates since v1.7.6 platforms with 64-bit long, which has been corrected. * Git now recognizes loose objects written by other implementations that - uses non-standard window size for zlib deflation (e.g. Agit running on + use a non-standard window size for zlib deflation (e.g. Agit running on Android with 4kb window). We used to reject anything that was not deflated with 32kb window. - * "git am" learned to pass "--exclude=<path>" option through to underlying + * Interaction between the use of pager and coloring of the output has + been improved, especially when a command that is not built-in was + involved. + + * "git am" learned to pass the "--exclude=<path>" option through to underlying "git apply". - * You can now feed many empty lines before feeding a mbox file to + * You can now feed many empty lines before feeding an mbox file to "git am". * "git archive" can be told to pass the output to gzip compression and produce "archive.tar.gz". - * "git bisect" can be used in a bare repository (provided if the test + * "git bisect" can be used in a bare repository (provided that the test you perform per each iteration does not need a working tree, of course). + * The length of abbreviated object names in "git branch -v" output + now honors the core.abbrev configuration variable. + * "git check-attr" can take relative paths from the command line. - * "git check-attr" learned "--all" option to list the attributes for a + * "git check-attr" learned an "--all" option to list the attributes for a given path. * "git checkout" (both the code to update the files upon checking out a - different branch, the code to checkout specific set of files) learned + different branch and the code to checkout a specific set of files) learned to stream the data from object store when possible, without having to - read the entire contents of a file in memory first. An earlier round + read the entire contents of a file into memory first. An earlier round of this code that is not in any released version had a large leak but now it has been plugged. - * "git clone" can now take "--config key=value" option to set the + * "git clone" can now take a "--config key=value" option to set the repository configuration options that affect the initial checkout. * "git commit <paths>..." now lets you feed relative pathspecs that - refer outside your current subdirectory. + refer to outside your current subdirectory. - * "git diff --stat" learned --stat-count option to limit the output of - diffstat report. + * "git diff --stat" learned a --stat-count option to limit the output of + a diffstat report. - * "git diff" learned "--histogram" option, to use a different diff + * "git diff" learned a "--histogram" option to use a different diff generation machinery stolen from jgit, which might give better performance. + * "git diff" had a weird worst case behaviour that can be triggered + when comparing files with potentially many places that could match. + * "git fetch", "git push" and friends no longer show connection - errors for addresses that couldn't be connected when at least one + errors for addresses that couldn't be connected to when at least one address succeeds (this is arguably a regression but a deliberate one). - * "git grep" learned --break and --heading options, to let users mimic - output format of "ack". + * "git grep" learned "--break" and "--heading" options, to let users mimic + the output format of "ack". - * "git grep" learned "-W" option that shows wider context using the same + * "git grep" learned a "-W" option that shows wider context using the same logic used by "git diff" to determine the hunk header. + * Invoking the low-level "git http-fetch" without "-a" option (which + git itself never did---normal users should not have to worry about + this) is now deprecated. + + * The "--decorate" option to "git log" and its family learned to + highlight grafted and replaced commits. + * "git rebase master topci" no longer spews usage hints after giving - "fatal: no such branch: topci" error message. + the "fatal: no such branch: topci" error message. + + * The recursive merge strategy implementation got a fairly large + fix for many corner cases that may rarely happen in real world + projects (it has been verified that none of the 16000+ merges in + the Linux kernel history back to v2.6.12 is affected with the + corner case bugs this update fixes). - * "git stash" learned --include-untracked option. + * "git stash" learned an "--include-untracked option". * "git submodule update" used to stop at the first error updating a submodule; it now goes on to update other submodules that can be updated, and reports the ones with errors at the end. - * "git upload-pack" and "git receive-pack" learned to pretend only a + * "git push" can be told with the "--recurse-submodules=check" option to + refuse pushing of the supermodule, if any of its submodules' + commits hasn't been pushed out to their remotes. + + * "git upload-pack" and "git receive-pack" learned to pretend that only a subset of the refs exist in a repository. This may help a site to put many tiny repositories into one repository (this would not be useful for larger repositories as repacking would be problematic). @@ -91,7 +118,7 @@ Updates since v1.7.6 that is more efficient in reading objects in packfiles. * test scripts for gitweb tried to run even when CGI-related perl modules - are not installed; it now exits early when they are unavailable. + are not installed; they now exit early when the latter are unavailable. Also contains various documentation updates and minor miscellaneous changes. @@ -100,17 +127,8 @@ changes. Fixes since v1.7.6 ------------------ -Unless otherwise noted, all the fixes in 1.7.6.X maintenance track are +Unless otherwise noted, all fixes in the 1.7.6.X maintenance track are included in this release. - * "ls-files ../$path" that is run from a subdirectory reported errors - incorrectly when there is no such path that matches the given pathspec. - (merge 0f64bfa cb/maint-ls-files-error-report later to 'maint'). - --- -exec >/var/tmp/1 -echo O=$(git describe master) -O=v1.7.6-576-g6fcb384 -git log --first-parent --oneline $O..master -echo -git shortlog --no-merges ^maint ^$O master + * "git branch -m" and "git checkout -b" incorrectly allowed the tip + of the branch that is currently checked out updated. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 938eccf2a5..0dbf2c9843 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -134,8 +134,7 @@ Another thing: NULL pointers shall be written as NULL, not as 0. (2) Generate your patch using git tools out of your commits. -git based diff tools (git, Cogito, and StGIT included) generate -unidiff which is the preferred format. +git based diff tools generate unidiff which is the preferred format. You do not have to be afraid to use -M option to "git diff" or "git format-patch", if your patch involves file renames. The diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index c50f189827..507b8d0ab2 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -113,7 +113,8 @@ OPTIONS --abbrev=<length>:: Alter the sha1's minimum display length in the output listing. - The default value is 7. + The default value is 7 and can be overridden by the `core.abbrev` + config option. --no-abbrev:: Display the full sha1s in the output listing rather than abbreviating them. diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 974e04ef1a..79fb984144 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -47,12 +47,14 @@ OPTIONS -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. + In addition to those found in .gitignore (per directory) and + $GIT_DIR/info/exclude, also consider these patterns to be in the + set of the ignore rules in effect. -x:: - Don't use the ignore rules. This allows removing all untracked + Don't use the standard ignore rules read from .gitignore (per + directory) and $GIT_DIR/info/exclude, but do still use the ignore + rules given with `-e` options. This allows removing all untracked files, including build products. This can be used (possibly in conjunction with 'git reset') to create a pristine working directory to test a clean build. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index ebd13be72e..69a1e4af9e 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -93,14 +93,14 @@ OPTIONS Listen on an alternative port. Incompatible with '--inetd' option. --init-timeout=<n>:: - Timeout between the moment the connection is established and the - client request is received (typically a rather low value, since + Timeout (in seconds) between the moment the connection is established + and the client request is received (typically a rather low value, since that should be basically immediate). --timeout=<n>:: - Timeout for specific client sub-requests. This includes the time - it takes for the server to process the sub-request and the time spent - waiting for the next client's request. + Timeout (in seconds) for specific client sub-requests. This includes + the time it takes for the server to process the sub-request and the + time spent waiting for the next client's request. --max-connections=<n>:: Maximum number of concurrent clients, defaults to 32. Set it to diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index db0d75fdbc..ec6ef31197 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -425,8 +425,8 @@ Here `<name>` is the person's display name (for example (``cm@example.com''). `LT` and `GT` are the literal less-than (\x3c) and greater-than (\x3e) symbols. These are required to delimit the email address from the other fields in the line. Note that -`<name>` is free-form and may contain any sequence of bytes, except -`LT` and `LF`. It is typically UTF-8 encoded. +`<name>` and `<email>` are free-form and may contain any sequence +of bytes, except `LT`, `GT` and `LF`. `<name>` is typically UTF-8 encoded. The time of the change is specified by `<when>` using the date format that was selected by the \--date-format=<fmt> command line option. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index d13c9b23f7..6ea9be775c 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -166,15 +166,22 @@ will want to ensure that threading is disabled for `git send-email`. --to=<email>:: Add a `To:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. + The negated form `--no-to` discards all `To:` headers added so + far (from config or command line). --cc=<email>:: Add a `Cc:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. + The negated form `--no-cc` discards all `Cc:` headers added so + far (from config or command line). --add-header=<header>:: Add an arbitrary header to the email headers. This is in addition to any configured headers, and may be used multiple times. - For example, `--add-header="Organization: git-foo"` + For example, `--add-header="Organization: git-foo"`. + The negated form `--no-add-header` discards *all* (`To:`, + `Cc:`, and custom) headers added so far from config or command + line. --cover-letter:: In addition to the patches, generate a cover letter file diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index 4d42073867..070cd1e6ed 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -15,6 +15,9 @@ DESCRIPTION ----------- Downloads a remote git repository via HTTP. +*NOTE*: use of this command without -a is deprecated. The -a +behaviour will become the default in a future release. + OPTIONS ------- commit-id:: diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 6c934660d7..249fc878ec 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -69,10 +69,13 @@ produced by --stat etc. its size is not included. [\--] <path>...:: - Show only commits that affect any of the specified paths. To - prevent confusion with options and branch names, paths may need - to be prefixed with "\-- " to separate them from options or - refnames. + Show only commits that are enough to explain how the files + that match the specified paths came to be. See "History + Simplification" below for details and other simplification + modes. ++ +To prevent confusion with options and branch names, paths may need to +be prefixed with "\-- " to separate them from options or refnames. include::rev-list-options.txt[] diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 6a187f2e23..e8319eac69 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -142,8 +142,9 @@ OPTIONS -C <object>:: --reuse-message=<object>:: - Take the note message from the given blob object (for - example, another note). + Take the given blob object (for example, another note) as the + note message. (Use `git notes copy <object>` instead to + copy notes between objects.) -c <object>:: --reedit-message=<object>:: @@ -285,6 +286,8 @@ $ blob=$(git hash-object -w a.out) $ git notes --ref=built add -C "$blob" HEAD ------------ +(You cannot simply use `git notes --ref=built add -F a.out HEAD` +because that is not binary-safe.) Of course, it doesn't make much sense to display non-text-format notes with 'git log', so if you use such notes, you'll probably need to write some special-purpose tools to do something useful with them. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 49c6e9fa51..aede48877f 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -162,6 +162,12 @@ useful if you write an alias or script around 'git push'. is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. +--recurse-submodules=check:: + Check whether all submodule commits used by the revisions to be + pushed are available on a remote tracking branch. Otherwise the + push will be aborted and the command will exit with non-zero status. + + include::urls-remotes.txt[] OUTPUT diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index a3a1d8eea3..b1f7dc643a 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -9,7 +9,7 @@ git-receive-pack - Receive what is pushed into the repository SYNOPSIS -------- [verse] -'git-receive-pack' [--quiet] <directory> +'git-receive-pack' <directory> DESCRIPTION ----------- @@ -35,9 +35,6 @@ are not fast-forwards. OPTIONS ------- ---quiet:: - Print only error messages. - <directory>:: The repository to sync into. diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 4f83dea5a3..674797cd83 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -24,22 +24,141 @@ output. Because a remote helper runs as an independent process from git, there is no need to re-link git to add a new helper, nor any need to link the helper with the implementation of git. -Every helper must support the "capabilities" command, which git will -use to determine what other commands the helper will accept. Other -commands generally concern facilities like discovering and updating -remote refs, transporting objects between the object database and -the remote repository, and updating the local object store. - -Helpers supporting the 'fetch' capability can discover refs from the -remote repository and transfer objects reachable from those refs to -the local object store. Helpers supporting the 'push' capability can -transfer local objects to the remote repository and update remote refs. +Every helper must support the "capabilities" command, which git +uses to determine what other commands the helper will accept. Those +other commands can be used to discover and update remote refs, +transport objects between the object database and the remote repository, +and update the local object store. Git comes with a "curl" family of remote helpers, that handle various transport protocols, such as 'git-remote-http', 'git-remote-https', 'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities 'fetch', 'option', and 'push'. +INPUT FORMAT +------------ + +Git sends the remote helper a list of commands on standard input, one +per line. The first command is always the 'capabilities' command, in +response to which the remote helper must print a list of the +capabilities it supports (see below) followed by a blank line. The +response to the capabilities command determines what commands Git uses +in the remainder of the command stream. + +The command stream is terminated by a blank line. In some cases +(indicated in the documentation of the relevant commands), this blank +line is followed by a payload in some other protocol (e.g., the pack +protocol), while in others it indicates the end of input. + +Capabilities +~~~~~~~~~~~~ + +Each remote helper is expected to support only a subset of commands. +The operations a helper supports are declared to git in the response +to the `capabilities` command (see COMMANDS, below). + +'option':: + For specifying settings like `verbosity` (how much output to + write to stderr) and `depth` (how much history is wanted in the + case of a shallow clone) that affect how other commands are + carried out. + +'connect':: + For fetching and pushing using git's native packfile protocol + that requires a bidirectional, full-duplex connection. + +'push':: + For listing remote refs and pushing specified objects from the + local object store to remote refs. + +'fetch':: + For listing remote refs and fetching the associated history to + the local object store. + +'import':: + For listing remote refs and fetching the associated history as + a fast-import stream. + +'refspec' <refspec>:: + This modifies the 'import' capability, allowing the produced + fast-import stream to modify refs in a private namespace + instead of writing to refs/heads or refs/remotes directly. + It is recommended that all importers providing the 'import' + capability use this. ++ +A helper advertising the capability +`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}` +is saying that, when it is asked to `import refs/heads/topic`, the +stream it outputs will update the `refs/svn/origin/branches/topic` +ref. ++ +This capability can be advertised multiple times. The first +applicable refspec takes precedence. The left-hand of refspecs +advertised with this capability must cover all refs reported by +the list command. If no 'refspec' capability is advertised, +there is an implied `refspec {asterisk}:{asterisk}`. + +Capabilities for Pushing +~~~~~~~~~~~~~~~~~~~~~~~~ +'connect':: + Can attempt to connect to 'git receive-pack' (for pushing), + 'git upload-pack', etc for communication using the + packfile protocol. ++ +Supported commands: 'connect'. + +'push':: + Can discover remote refs and push local commits and the + history leading up to them to new or existing remote refs. ++ +Supported commands: 'list for-push', 'push'. + +If a helper advertises both 'connect' and 'push', git will use +'connect' if possible and fall back to 'push' if the helper requests +so when connecting (see the 'connect' command under COMMANDS). + +Capabilities for Fetching +~~~~~~~~~~~~~~~~~~~~~~~~~ +'connect':: + Can try to connect to 'git upload-pack' (for fetching), + 'git receive-pack', etc for communication using the + packfile protocol. ++ +Supported commands: 'connect'. + +'fetch':: + Can discover remote refs and transfer objects reachable from + them to the local object store. ++ +Supported commands: 'list', 'fetch'. + +'import':: + Can discover remote refs and output objects reachable from + them as a stream in fast-import format. ++ +Supported commands: 'list', 'import'. + +If a helper advertises 'connect', git will use it if possible and +fall back to another capability if the helper requests so when +connecting (see the 'connect' command under COMMANDS). +When choosing between 'fetch' and 'import', git prefers 'fetch'. +Other frontends may have some other order of preference. + +'refspec' <refspec>:: + This modifies the 'import' capability. ++ +A helper advertising +`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}` +in its capabilities is saying that, when it handles +`import refs/heads/topic`, the stream it outputs will update the +`refs/svn/origin/branches/topic` ref. ++ +This capability can be advertised multiple times. The first +applicable refspec takes precedence. The left-hand of refspecs +advertised with this capability must cover all refs reported by +the list command. If no 'refspec' capability is advertised, +there is an implied `refspec {asterisk}:{asterisk}`. + INVOCATION ---------- @@ -122,7 +241,22 @@ Supported if the helper has the "fetch" capability. 'push' +<src>:<dst>:: Pushes the given local <src> commit or branch to the remote branch described by <dst>. A batch sequence of - one or more push commands is terminated with a blank line. + one or more 'push' commands is terminated with a blank line + (if there is only one reference to push, a single 'push' command + is followed by a blank line). For example, the following would + be two batches of 'push', the first asking the remote-helper + to push the local ref 'master' to the remote ref 'master' and + the local 'HEAD' to the remote 'branch', and the second + asking to push ref 'foo' to ref 'bar' (forced update requested + by the '+'). ++ +------------ +push refs/heads/master:refs/heads/master +push HEAD:refs/heads/branch +\n +push +refs/heads/foo:refs/heads/bar +\n +------------ + Zero or more protocol options may be entered after the last 'push' command, before the batch's terminating blank line. @@ -147,6 +281,11 @@ Supported if the helper has the "push" capability. Especially useful for interoperability with a foreign versioning system. + +Just like 'push', a batch sequence of one or more 'import' is +terminated with a blank line. For each batch of 'import', the remote +helper should produce a fast-import stream terminated by a 'done' +command. ++ Supported if the helper has the "import" capability. 'connect' <service>:: @@ -171,26 +310,6 @@ completing a valid response for the current command. Additional commands may be supported, as may be determined from capabilities reported by the helper. -CAPABILITIES ------------- - -'fetch':: -'option':: -'push':: -'import':: -'connect':: - This helper supports the corresponding command with the same name. - -'refspec' 'spec':: - When using the import command, expect the source ref to have - been written to the destination ref. The earliest applicable - refspec takes precedence. For example - "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means - that, after an "import refs/heads/name", the script has written to - refs/svn/origin/branches/name. If this capability is used at - all, it must cover all refs reported by the list command; if - it is not used, it is effectively "{asterisk}:{asterisk}" - REF LIST ATTRIBUTES ------------------- @@ -243,6 +362,8 @@ SEE ALSO -------- linkgit:git-remote[1] +linkgit:git-remote-testgit[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt new file mode 100644 index 0000000000..2a67d456a3 --- /dev/null +++ b/Documentation/git-remote-testgit.txt @@ -0,0 +1,30 @@ +git-remote-testgit(1) +===================== + +NAME +---- +git-remote-testgit - Example remote-helper + + +SYNOPSIS +-------- +[verse] +git clone testgit::<source-repo> [<destination>] + +DESCRIPTION +----------- + +This command is a simple remote-helper, that is used both as a +testcase for the remote-helper functionality, and as an example to +show remote-helper authors one possible implementation. + +The best way to learn more is to read the comments and source code in +'git-remote-testgit.py'. + +SEE ALSO +-------- +linkgit:git-remote-helpers[1] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index bed9e1f097..bd3eaa69bf 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -9,7 +9,7 @@ git-send-pack - Push objects over git protocol to another repository SYNOPSIS -------- [verse] -'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--quiet] [--verbose] [--thin] [<host>:]<directory> [<ref>...] +'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...] DESCRIPTION ----------- @@ -45,9 +45,6 @@ OPTIONS the remote repository can lose commits; use it with care. ---quiet:: - Print only error messages. - --verbose:: Run verbosely. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index ed5eca1fce..08cad6d2b6 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -157,6 +157,17 @@ Skip "branches" and "tags" of first level directories;; affecting the working tree; and the 'rebase' command will be able to update the working tree with the latest changes. +--preserve-empty-dirs;; + Create a placeholder file in the local Git repository for each + empty directory fetched from Subversion. This includes directories + that become empty by removing all entries in the Subversion + repository (but not the directory itself). The placeholder files + are also tracked and removed when no longer necessary. + +--placeholder-filename=<filename>;; + Set the name of placeholder files created by --preserve-empty-dirs. + Default: ".gitignore" + 'rebase':: This fetches revisions from the SVN parent of the current HEAD and rebases the current (uncommitted to SVN) work against it. @@ -211,8 +222,17 @@ discouraged. Add the given merge information during the dcommit (e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can store this information (as a property), and svn clients starting from - version 1.5 can make use of it. 'git svn' currently does not use it - and does not set it automatically. + version 1.5 can make use of it. To specify merge information from multiple + branches, use a single space character between the branches + (`--mergeinfo="/branches/foo:1-10 /branches/bar:3,5-6,8"`) ++ +[verse] +config key: svn.pushmergeinfo ++ +This option will cause git-svn to attempt to automatically populate the +svn:mergeinfo property in the SVN repository when possible. Currently, this can +only be done when dcommitting non-fast-forward merges where all parents but the +first have already been pushed into SVN. 'branch':: Create a branch in the SVN repository. diff --git a/Documentation/git.txt b/Documentation/git.txt index d08a8bb4f2..cbc51d5a94 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,10 +44,18 @@ 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.6.1/git.html[documentation for release 1.7.6.1] +* link:v1.7.7/git.html[documentation for release 1.7.7] * release notes for - link:RelNotes/1.7.6.1.txt[1.7.6.1]. + link:RelNotes/1.7.7.txt[1.7.7]. + +* link:v1.7.6.4/git.html[documentation for release 1.7.6.4] + +* release notes for + link:RelNotes/1.7.6.4.txt[1.7.6.4], + link:RelNotes/1.7.6.3.txt[1.7.6.3], + link:RelNotes/1.7.6.2.txt[1.7.6.2], + link:RelNotes/1.7.6.1.txt[1.7.6.1], link:RelNotes/1.7.6.txt[1.7.6]. * link:v1.7.5.4/git.html[documentation for release 1.7.5.4] diff --git a/Documentation/gitnamespaces.txt b/Documentation/gitnamespaces.txt index ed8924e856..c6713cf5d7 100644 --- a/Documentation/gitnamespaces.txt +++ b/Documentation/gitnamespaces.txt @@ -5,6 +5,13 @@ NAME ---- gitnamespaces - Git namespaces +SYNOPSIS +-------- +[verse] +GIT_NAMESPACE=<namespace> 'git upload-pack' +GIT_NAMESPACE=<namespace> 'git receive-pack' + + DESCRIPTION ----------- diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index eb3d040783..5c891f1169 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -23,32 +23,25 @@ objects:: Object store associated with this repository. Usually an object store is self sufficient (i.e. all the objects that are referred to by an object found in it are also - found in it), but there are couple of ways to violate - it. + found in it), but there are a few ways to violate it. + -. You could populate the repository by running a commit walker -without `-a` option. Depending on which options are given, you -could have only commit objects without associated blobs and -trees this way, for example. A repository with this kind of -incomplete object store is not suitable to be published to the -outside world but sometimes useful for private repository. -. You also could have an incomplete but locally usable repository -by cloning shallowly. See linkgit:git-clone[1]. -. You can be using `objects/info/alternates` mechanism, or -`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow' +. You could have an incomplete but locally usable repository +by creating a shallow clone. See linkgit:git-clone[1]. +. You could be using the `objects/info/alternates` or +`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanisms to 'borrow' objects from other object stores. A repository with this kind of incomplete object store is not suitable to be published for use with dumb transports but otherwise is OK as long as -`objects/info/alternates` points at the right object stores -it borrows from. +`objects/info/alternates` points at the object stores it +borrows from. objects/[0-9a-f][0-9a-f]:: - Traditionally, each object is stored in its own file. - They are split into 256 subdirectories using the first - two letters from its object name to keep the number of - directory entries `objects` directory itself needs to - hold. Objects found here are often called 'unpacked' - (or 'loose') objects. + A newly created object is stored in its own file. + The objects are splayed over 256 subdirectories using + the first two characters of the sha1 object name to + keep the number of directory entries in `objects` + itself to a manageable number. Objects found + here are often called 'unpacked' (or 'loose') objects. objects/pack:: Packs (files that store many object in compressed form, @@ -85,7 +78,7 @@ objects/info/http-alternates:: refs:: References are stored in subdirectories of this - directory. The 'git prune' command knows to keep + directory. The 'git prune' command knows to preserve objects reachable from refs found in this directory and its subdirectories. @@ -119,16 +112,17 @@ HEAD:: + HEAD can also record a specific commit directly, instead of being a symref to point at the current branch. Such a state -is often called 'detached HEAD', and almost all commands work -identically as normal. See linkgit:git-checkout[1] for -details. +is often called 'detached HEAD.' See linkgit:git-checkout[1] +for details. branches:: A slightly deprecated way to store shorthands to be used - to specify URL to 'git fetch', 'git pull' and 'git push' - commands is to store a file in `branches/<name>` and - give 'name' to these commands in place of 'repository' - argument. + to specify a URL to 'git fetch', 'git pull' and 'git push'. + A file can be stored as `branches/<name>` and then + 'name' can be given to these commands in place of + 'repository' argument. See the REMOTES section in + linkgit:git-fetch[1] for details. This mechanism is legacy + and not likely to be found in modern repositories. hooks:: Hooks are customization scripts used by various git @@ -173,9 +167,11 @@ info/exclude:: at it. See also: linkgit:gitignore[5]. remotes:: - Stores shorthands to be used to give URL and default - refnames to interact with remote repository to - 'git fetch', 'git pull' and 'git push' commands. + Stores shorthands for URL and default refnames for use + when interacting with remote repositories via 'git fetch', + 'git pull' and 'git push' commands. See the REMOTES section + in linkgit:git-fetch[1] for details. This mechanism is legacy + and not likely to be found in modern repositories. logs:: Records of changes made to refs are stored in this diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index d527b30770..8823a37067 100644 --- a/Documentation/howto/maintain-git.txt +++ b/Documentation/howto/maintain-git.txt @@ -176,7 +176,7 @@ by doing the following: - Update "What's cooking" message to review the updates to existing topics, newly added topics and graduated topics. - This step is helped with Meta/UWC script (where Meta/ contains + This step is helped with Meta/cook script (where Meta/ contains a checkout of the 'todo' branch). - Merge topics to 'next'. For each branch whose tip is not @@ -197,10 +197,9 @@ by doing the following: - Nothing is next-worthy; do not do anything. - - Rebase topics that do not have any commit in next yet. This - step is optional but sometimes is worth doing when an old - series that is not in next can take advantage of low-level - framework change that is merged to 'master' already. + - [** OBSOLETE **] Optionally rebase topics that do not have any commit + in next yet, when they can take advantage of low-level framework + change that is merged to 'master' already. $ git rebase master ai/topic @@ -209,7 +208,7 @@ by doing the following: pre-rebase hook to make sure that topics that are already in 'next' are not rebased beyond the merged commit. - - Rebuild "pu" to merge the tips of topics not in 'next'. + - [** OBSOLETE **] Rebuild "pu" to merge the tips of topics not in 'next'. $ git checkout pu $ git reset --hard next @@ -241,7 +240,7 @@ by doing the following: - Fetch html and man branches back from k.org, and push four integration branches and the two documentation branches to - repo.or.cz + repo.or.cz and other mirrors. Some observations to be made. diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index 3f575bdcff..ce24eb96f5 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the . Can sort an unsorted list using `sort_string_list`. +. Can remove individual items of an unsorted list using + `unsorted_string_list_delete_item`. + . Finally it should free the list using `string_list_clear`. Example: @@ -112,6 +115,13 @@ write `string_list_insert(...)->util = ...;`. The above two functions need to look through all items, as opposed to their counterpart for sorted lists, which performs a binary search. +`unsorted_string_list_delete_item`:: + + Remove an item from a string_list. The `string` pointer of the items + will be freed in case the `strdup_strings` member of the string_list + is set. The third parameter controls if the `util` pointer of the + items should be freed or not. + Data structures --------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 8270abb5f9..f1dc5faead 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.6.GIT +DEF_VER=v1.7.7 LF=' ' @@ -515,6 +515,7 @@ LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h +LIB_H += compat/obstack.h LIB_H += compat/win32/pthread.h LIB_H += compat/win32/syslog.h LIB_H += compat/win32/sys/poll.h @@ -533,6 +534,7 @@ LIB_H += graph.h LIB_H += grep.h LIB_H += hash.h LIB_H += help.h +LIB_H += kwset.h LIB_H += levenshtein.h LIB_H += list-objects.h LIB_H += ll-merge.h @@ -594,6 +596,7 @@ LIB_OBJS += cache-tree.o LIB_OBJS += color.o LIB_OBJS += combine-diff.o LIB_OBJS += commit.o +LIB_OBJS += compat/obstack.o LIB_OBJS += config.o LIB_OBJS += connect.o LIB_OBJS += convert.o @@ -623,6 +626,7 @@ LIB_OBJS += hash.o LIB_OBJS += help.o LIB_OBJS += hex.o LIB_OBJS += ident.o +LIB_OBJS += kwset.o LIB_OBJS += levenshtein.o LIB_OBJS += list-objects.o LIB_OBJS += ll-merge.o @@ -1,3 +1,12 @@ +/* + * Handle git attributes. See gitattributes(5) for a description of + * the file syntax, and Documentation/technical/api-gitattributes.txt + * for a description of the API. + * + * One basic design decision here is that we are not going to support + * an insanely large number of attributes. + */ + #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "exec_cmd.h" @@ -13,12 +22,7 @@ static const char git_attr__unknown[] = "(builtin)unknown"; static const char *attributes_file; -/* - * The basic design decision here is that we are not going to have - * insanely large number of attributes. - * - * This is a randomly chosen prime. - */ +/* This is a randomly chosen prime. */ #define HASHSIZE 257 #ifndef DEBUG_ATTR @@ -106,22 +110,26 @@ struct git_attr *git_attr(const char *name) return git_attr_internal(name, strlen(name)); } -/* - * .gitattributes file is one line per record, each of which is - * - * (1) glob pattern. - * (2) whitespace - * (3) whitespace separated list of attribute names, each of which - * could be prefixed with '-' to mean "set to false", '!' to mean - * "unset". - */ - /* What does a matched pattern decide? */ struct attr_state { struct git_attr *attr; const char *setto; }; +/* + * One rule, as from a .gitattributes file. + * + * If is_macro is true, then u.attr is a pointer to the git_attr being + * defined. + * + * If is_macro is false, then u.pattern points at the filename pattern + * to which the rule applies. (The memory pointed to is part of the + * memory block allocated for the match_attr instance.) + * + * In either case, num_attr is the number of attributes affected by + * this rule, and state is an array listing them. The attributes are + * listed as they appear in the file (macros unexpanded). + */ struct match_attr { union { char *pattern; @@ -134,8 +142,15 @@ struct match_attr { static const char blank[] = " \t\r\n"; +/* + * Parse a whitespace-delimited attribute state (i.e., "attr", + * "-attr", "!attr", or "attr=value") from the string starting at src. + * If e is not NULL, write the results to *e. Return a pointer to the + * remainder of the string (with leading whitespace removed), or NULL + * if there was an error. + */ static const char *parse_attr(const char *src, int lineno, const char *cp, - int *num_attr, struct match_attr *res) + struct attr_state *e) { const char *ep, *equals; int len; @@ -148,7 +163,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, len = equals - cp; else len = ep - cp; - if (!res) { + if (!e) { if (*cp == '-' || *cp == '!') { cp++; len--; @@ -160,9 +175,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, return NULL; } } else { - struct attr_state *e; - - e = &(res->state[*num_attr]); if (*cp == '-' || *cp == '!') { e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET; cp++; @@ -175,7 +187,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, } e->attr = git_attr_internal(cp, len); } - (*num_attr)++; return ep + strspn(ep, blank); } @@ -183,10 +194,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, int lineno, int macro_ok) { int namelen; - int num_attr; - const char *cp, *name; + int num_attr, i; + const char *cp, *name, *states; struct match_attr *res = NULL; - int pass; int is_macro; cp = line + strspn(line, blank); @@ -215,32 +225,35 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, else is_macro = 0; - for (pass = 0; pass < 2; pass++) { - /* pass 0 counts and allocates, pass 1 fills */ - num_attr = 0; - cp = name + namelen; - cp = cp + strspn(cp, blank); - while (*cp) { - cp = parse_attr(src, lineno, cp, &num_attr, res); - if (!cp) - return NULL; - } - if (pass) - break; - res = xcalloc(1, - sizeof(*res) + - sizeof(struct attr_state) * num_attr + - (is_macro ? 0 : namelen + 1)); - if (is_macro) - res->u.attr = git_attr_internal(name, namelen); - else { - res->u.pattern = (char *)&(res->state[num_attr]); - memcpy(res->u.pattern, name, namelen); - res->u.pattern[namelen] = 0; - } - res->is_macro = is_macro; - res->num_attr = num_attr; + states = name + namelen; + states += strspn(states, blank); + + /* First pass to count the attr_states */ + for (cp = states, num_attr = 0; *cp; num_attr++) { + cp = parse_attr(src, lineno, cp, NULL); + if (!cp) + return NULL; + } + + res = xcalloc(1, + sizeof(*res) + + sizeof(struct attr_state) * num_attr + + (is_macro ? 0 : namelen + 1)); + if (is_macro) + res->u.attr = git_attr_internal(name, namelen); + else { + res->u.pattern = (char *)&(res->state[num_attr]); + memcpy(res->u.pattern, name, namelen); + res->u.pattern[namelen] = 0; } + res->is_macro = is_macro; + res->num_attr = num_attr; + + /* Second pass to fill the attr_states */ + for (cp = states, i = 0; *cp; i++) { + cp = parse_attr(src, lineno, cp, &(res->state[i])); + } + return res; } @@ -135,6 +135,28 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, return 0; } +int validate_new_branchname(const char *name, struct strbuf *ref, + int force, int attr_only) +{ + if (strbuf_check_branch_ref(ref, name)) + die("'%s' is not a valid branch name.", name); + + if (!ref_exists(ref->buf)) + return 0; + else if (!force && !attr_only) + die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/")); + + if (!attr_only) { + const char *head; + unsigned char sha1[20]; + + head = resolve_ref("HEAD", sha1, 0, NULL); + if (!is_bare_repository() && head && !strcmp(head, ref->buf)) + die("Cannot force update the current branch."); + } + return 1; +} + void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, enum branch_track track) @@ -151,17 +173,12 @@ void create_branch(const char *head, if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE) explicit_tracking = 1; - if (strbuf_check_branch_ref(&ref, name)) - die("'%s' is not a valid branch name.", name); - - if (resolve_ref(ref.buf, sha1, 1, NULL)) { - if (!force && track == BRANCH_TRACK_OVERRIDE) + if (validate_new_branchname(name, &ref, force, + track == BRANCH_TRACK_OVERRIDE)) { + if (!force) dont_change_ref = 1; - else if (!force) - die("A branch named '%s' already exists.", name); - else if (!is_bare_repository() && head && !strcmp(head, name)) - die("Cannot force update the current branch."); - forcing = 1; + else + forcing = 1; } real_ref = NULL; @@ -210,7 +227,7 @@ void create_branch(const char *head, start_name); if (real_ref && track) - setup_tracking(name, real_ref, track); + setup_tracking(ref.buf+11, real_ref, track); if (!dont_change_ref) if (write_ref_sha1(lock, sha1, msg) < 0) @@ -16,6 +16,24 @@ void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, enum branch_track track); /* + * Validates that the requested branch may be created, returning the + * interpreted ref in ref, force indicates whether (non-head) branches + * may be overwritten. A non-zero return value indicates that the force + * parameter was non-zero and the branch already exists. + * + * Contrary to all of the above, when attr_only is 1, the caller is + * not interested in verifying if it is Ok to update the named + * branch to point at a potentially different commit. It is merely + * asking if it is OK to change some attribute for the named branch + * (e.g. tracking upstream). + * + * NEEDSWORK: This needs to be split into two separate functions in the + * longer run for sanity. + * + */ +int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only); + +/* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) */ diff --git a/builtin/branch.c b/builtin/branch.c index 3142daa57a..f49596f826 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -71,7 +71,7 @@ static int parse_branch_color_slot(const char *var, int ofs) static int git_branch_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value, -1); + branch_use_color = git_config_colorbool(var, value); return 0; } if (!prefixcmp(var, "color.branch.")) { @@ -88,7 +88,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) static const char *branch_get_color(enum color_branch ix) { - if (branch_use_color > 0) + if (want_color(branch_use_color)) return branch_colors[ix]; return ""; } @@ -566,11 +566,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) die(_("Invalid branch name: '%s'"), oldname); } - if (strbuf_check_branch_ref(&newref, newname)) - die(_("Invalid branch name: '%s'"), newname); - - if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die(_("A branch named '%s' already exists."), newref.buf + 11); + validate_new_branchname(newname, &newref, force, 0); strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); @@ -613,7 +609,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0; - int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; + int verbose = 0, abbrev = -1, detached = 0; int reflog = 0; enum branch_track track; int kinds = REF_LOCAL_BRANCH; @@ -673,9 +669,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) git_config(git_branch_config, NULL); - if (branch_use_color == -1) - branch_use_color = git_use_color_default; - track = git_branch_track; head = resolve_ref("HEAD", head_sha1, 0, NULL); @@ -696,6 +689,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!!delete + !!rename + !!force_create > 1) usage_with_options(builtin_branch_usage, options); + if (abbrev == -1) + abbrev = DEFAULT_ABBREV; + if (delete) return delete_branches(argc, argv, delete > 1, kinds); else if (argc == 0) diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index ae3f28115a..0723cf245e 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -12,8 +12,8 @@ static const char builtin_check_ref_format_usage[] = " or: git check-ref-format --branch <branchname-shorthand>"; /* - * Replace each run of adjacent slashes in src with a single slash, - * and write the result to dst. + * Remove leading slashes and replace each run of adjacent slashes in + * src with a single slash, and write the result to dst. * * This function is similar to normalize_path_copy(), but stripped down * to meet check_ref_format's simpler needs. @@ -21,7 +21,7 @@ static const char builtin_check_ref_format_usage[] = static void collapse_slashes(char *dst, const char *src) { char ch; - char prev = '\0'; + char prev = '/'; while ((ch = *src++) != '\0') { if (prev == '/' && ch == prev) diff --git a/builtin/checkout.c b/builtin/checkout.c index 4eaedff0c4..5e356a6c61 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1072,15 +1072,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - 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 (ref_exists(buf.buf)) { - opts.branch_exists = 1; - if (!opts.new_branch_force) - die(_("git checkout: branch %s already exists"), - opts.new_branch); - } + + opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, + !!opts.new_branch_force, 0); + strbuf_release(&buf); } diff --git a/builtin/clean.c b/builtin/clean.c index 75697f7111..0c7b3d0f4c 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -54,7 +54,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", - "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb }, + "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb }, OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), OPT_BOOLEAN('X', NULL, &ignored_only, "remove only ignored files"), @@ -98,7 +98,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) setup_standard_excludes(&dir); for (i = 0; i < exclude_list.nr; i++) - add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list); + add_exclude(exclude_list.items[i].string, "", 0, + &dir.exclude_list[EXC_CMDL]); pathspec = get_pathspec(prefix, argv); diff --git a/builtin/clone.c b/builtin/clone.c index 7663bc22c9..488f48e9a5 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -39,7 +39,7 @@ static const char * const builtin_clone_usage[] = { static int option_no_checkout, option_bare, option_mirror; static int option_local, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_reference, *option_depth; +static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; static const char *real_git_dir; @@ -47,6 +47,16 @@ static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress; static struct string_list option_config; +static struct string_list option_reference; + +static int opt_parse_reference(const struct option *opt, const char *arg, int unset) +{ + struct string_list *option_reference = opt->value; + if (!arg) + return -1; + string_list_append(option_reference, arg); + return 0; +} static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -72,8 +82,8 @@ static struct option builtin_clone_options[] = { "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "template-directory", "directory from which templates will be used"), - OPT_STRING(0, "reference", &option_reference, "repo", - "reference repository"), + OPT_CALLBACK(0 , "reference", &option_reference, "repo", + "reference repository", &opt_parse_reference), OPT_STRING('o', "origin", &option_origin, "branch", "use <branch> instead of 'origin' to track upstream"), OPT_STRING('b', "branch", &option_branch, "branch", @@ -103,9 +113,26 @@ static char *get_repo_path(const char *repo, int *is_bundle) for (i = 0; i < ARRAY_SIZE(suffix); i++) { const char *path; path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { + if (stat(path, &st)) + continue; + if (S_ISDIR(st.st_mode)) { *is_bundle = 0; return xstrdup(absolute_path(path)); + } else if (S_ISREG(st.st_mode) && st.st_size > 8) { + /* Is it a "gitfile"? */ + char signature[8]; + int len, fd = open(path, O_RDONLY); + if (fd < 0) + continue; + len = read_in_full(fd, signature, 8); + close(fd); + if (len != 8 || strncmp(signature, "gitdir: ", 8)) + continue; + path = read_gitfile(path); + if (path) { + *is_bundle = 0; + return xstrdup(absolute_path(path)); + } } } @@ -199,39 +226,80 @@ static void strip_trailing_slashes(char *dir) *end = '\0'; } -static void setup_reference(const char *repo) +static int add_one_reference(struct string_list_item *item, void *cb_data) { - const char *ref_git; - char *ref_git_copy; - + char *ref_git; + struct strbuf alternate = STRBUF_INIT; struct remote *remote; struct transport *transport; const struct ref *extra; - ref_git = real_path(option_reference); - - if (is_directory(mkpath("%s/.git/objects", ref_git))) - ref_git = mkpath("%s/.git", ref_git); - else if (!is_directory(mkpath("%s/objects", ref_git))) + /* Beware: real_path() and mkpath() return static buffer */ + ref_git = xstrdup(real_path(item->string)); + if (is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git)); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) die(_("reference repository '%s' is not a local directory."), - option_reference); + item->string); - ref_git_copy = xstrdup(ref_git); + strbuf_addf(&alternate, "%s/objects", ref_git); + add_to_alternates_file(alternate.buf); + strbuf_release(&alternate); - add_to_alternates_file(ref_git_copy); - - remote = remote_get(ref_git_copy); - transport = transport_get(remote, ref_git_copy); + remote = remote_get(ref_git); + transport = transport_get(remote, ref_git); for (extra = transport_get_remote_refs(transport); extra; extra = extra->next) add_extra_ref(extra->name, extra->old_sha1, 0); transport_disconnect(transport); + free(ref_git); + return 0; +} - free(ref_git_copy); +static void setup_reference(void) +{ + for_each_string_list(&option_reference, add_one_reference, NULL); } -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) +static void copy_alternates(struct strbuf *src, struct strbuf *dst, + const char *src_repo) +{ + /* + * Read from the source objects/info/alternates file + * and copy the entries to corresponding file in the + * destination repository with add_to_alternates_file(). + * Both src and dst have "$path/objects/info/alternates". + * + * Instead of copying bit-for-bit from the original, + * we need to append to existing one so that the already + * created entry via "clone -s" is not lost, and also + * to turn entries with paths relative to the original + * absolute, so that they can be used in the new repository. + */ + FILE *in = fopen(src->buf, "r"); + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, in, '\n') != EOF) { + char *abs_path, abs_buf[PATH_MAX]; + if (!line.len || line.buf[0] == '#') + continue; + if (is_absolute_path(line.buf)) { + add_to_alternates_file(line.buf); + continue; + } + abs_path = mkpath("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_buf, abs_path); + add_to_alternates_file(abs_buf); + } + strbuf_release(&line); + fclose(in); +} + +static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + const char *src_repo, int src_baselen) { struct dirent *de; struct stat buf; @@ -267,7 +335,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (S_ISDIR(buf.st_mode)) { if (de->d_name[0] != '.') - copy_or_link_directory(src, dest); + copy_or_link_directory(src, dest, + src_repo, src_baselen); + continue; + } + + /* Files that cannot be copied bit-for-bit... */ + if (!strcmp(src->buf + src_baselen, "/info/alternates")) { + copy_alternates(src, dest, src_repo); continue; } @@ -290,17 +365,20 @@ static const struct ref *clone_local(const char *src_repo, const char *dest_repo) { const struct ref *ret; - struct strbuf src = STRBUF_INIT; - struct strbuf dest = STRBUF_INIT; struct remote *remote; struct transport *transport; - if (option_shared) - add_to_alternates_file(src_repo); - else { + if (option_shared) { + struct strbuf alt = STRBUF_INIT; + strbuf_addf(&alt, "%s/objects", src_repo); + add_to_alternates_file(alt.buf); + strbuf_release(&alt); + } else { + struct strbuf src = STRBUF_INIT; + struct strbuf dest = STRBUF_INIT; strbuf_addf(&src, "%s/objects", src_repo); strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); + copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); } @@ -544,8 +622,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference) - setup_reference(git_dir); + if (option_reference.nr) + setup_reference(); fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); diff --git a/builtin/commit.c b/builtin/commit.c index 05029d40d1..cbc9613ec6 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1141,7 +1141,7 @@ static int git_status_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); + s->use_color = git_config_colorbool(k, v); return 0; } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { @@ -1234,10 +1234,6 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (s.relative_paths) s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; switch (status_format) { case STATUS_FORMAT_SHORT: @@ -1393,8 +1389,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_config(git_commit_config, &s); determine_whence(&s); - if (s.use_color == -1) - s.use_color = git_use_color_default; if (get_sha1("HEAD", sha1)) current_head = NULL; else { @@ -1404,11 +1398,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix, current_head, &s); - if (dry_run) { - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; + if (dry_run) return dry_run_commit(argc, argv, prefix, current_head, &s); - } index_file = prepare_index(argc, argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes diff --git a/builtin/config.c b/builtin/config.c index 211e118d57..0b4ecac855 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -303,24 +303,18 @@ static void get_color(const char *def_color) fputs(parsed_color, stdout); } -static int stdout_is_tty; static int get_colorbool_found; static int get_diff_color_found; +static int get_color_ui_found; static int git_get_colorbool_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, get_colorbool_slot)) { - get_colorbool_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "diff.color")) { - get_diff_color_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "color.ui")) { - git_use_color_default = git_config_colorbool(var, value, stdout_is_tty); - return 0; - } + if (!strcmp(var, get_colorbool_slot)) + get_colorbool_found = git_config_colorbool(var, value); + else if (!strcmp(var, "diff.color")) + get_diff_color_found = git_config_colorbool(var, value); + else if (!strcmp(var, "color.ui")) + get_color_ui_found = git_config_colorbool(var, value); return 0; } @@ -334,9 +328,11 @@ static int get_colorbool(int print) if (!strcmp(get_colorbool_slot, "color.diff")) get_colorbool_found = get_diff_color_found; if (get_colorbool_found < 0) - get_colorbool_found = git_use_color_default; + get_colorbool_found = get_color_ui_found; } + get_colorbool_found = want_color(get_colorbool_found); + if (print) { printf("%s\n", get_colorbool_found ? "true" : "false"); return 0; @@ -510,9 +506,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_GET_COLORBOOL) { if (argc == 1) - stdout_is_tty = git_config_bool("command line", argv[0]); - else if (argc == 0) - stdout_is_tty = isatty(1); + color_stdout_is_tty = git_config_bool("command line", argv[0]); return get_colorbool(argc != 0); } diff --git a/builtin/diff.c b/builtin/diff.c index 69cd5eed78..1118689fb2 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -277,9 +277,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix) gitmodules_config(); git_config(git_diff_ui_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); /* If this is a no-index diff, just run it and exit there. */ diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 3c871c2da8..412bd327b5 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -185,6 +185,36 @@ static void consume_shallow_list(int fd) } } +struct write_shallow_data { + struct strbuf *out; + int use_pack_protocol; + int count; +}; + +static int write_one_shallow(const struct commit_graft *graft, void *cb_data) +{ + struct write_shallow_data *data = cb_data; + const char *hex = sha1_to_hex(graft->sha1); + data->count++; + if (data->use_pack_protocol) + packet_buf_write(data->out, "shallow %s", hex); + else { + strbuf_addstr(data->out, hex); + strbuf_addch(data->out, '\n'); + } + return 0; +} + +static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) +{ + struct write_shallow_data data; + data.out = out; + data.use_pack_protocol = use_pack_protocol; + data.count = 0; + for_each_commit_graft(write_one_shallow, &data); + return data.count; +} + static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; diff --git a/builtin/fetch.c b/builtin/fetch.c index 93c99385a9..e422ced929 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -941,6 +941,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { + if (recurse_submodules_default) { + int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); + set_config_fetch_recurse_submodules(arg); + } + gitmodules_config(); + git_config(submodule_config, NULL); + } + if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument")); @@ -976,12 +985,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { const char *options[10]; int num_options = 0; - if (recurse_submodules_default) { - int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); - set_config_fetch_recurse_submodules(arg); - } - gitmodules_config(); - git_config(submodule_config, NULL); add_options_to_argv(&num_options, options); result = fetch_populated_submodules(num_options, options, submodule_prefix, diff --git a/builtin/grep.c b/builtin/grep.c index 1851797540..a286692e46 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -325,7 +325,7 @@ static int grep_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.grep")) - opt->color = git_config_colorbool(var, value, -1); + opt->color = git_config_colorbool(var, value); else if (!strcmp(var, "color.grep.context")) color = opt->color_context; else if (!strcmp(var, "color.grep.filename")) @@ -598,8 +598,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; + read_sha1_lock(); data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); + read_sha1_unlock(); + if (!data) die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); @@ -898,8 +901,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) strcpy(opt.color_sep, GIT_COLOR_CYAN); opt.color = -1; git_config(grep_config, &opt); - if (opt.color == -1) - opt.color = git_use_color_default; /* * If there is no -- then the paths must exist in the working diff --git a/builtin/init-db.c b/builtin/init-db.c index 025aa47c80..d07554c884 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -347,7 +347,7 @@ static void separate_git_dir(const char *git_dir) const char *src; if (S_ISREG(st.st_mode)) - src = read_gitfile_gently(git_link); + src = read_gitfile(git_link); else if (S_ISDIR(st.st_mode)) src = git_link; else diff --git a/builtin/log.c b/builtin/log.c index 5c2af59004..f5d4930590 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -359,9 +359,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; @@ -446,9 +443,6 @@ int cmd_show(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_pathspec(&match_all, NULL); init_revisions(&rev, prefix); rev.diff = 1; @@ -524,9 +518,6 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); rev.verbose_header = 1; @@ -549,9 +540,6 @@ int cmd_log(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); @@ -620,7 +608,8 @@ static int git_format_config(const char *var, const char *value, void *cb) string_list_append(&extra_cc, value); return 0; } - if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") || + !strcmp(var, "color.ui")) { return 0; } if (!strcmp(var, "format.numbered")) { diff --git a/builtin/merge.c b/builtin/merge.c index 325891edb6..ab4077f272 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -390,8 +390,6 @@ static void finish(const unsigned char *new_head, const char *msg) opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); if (diff_setup_done(&opts) < 0) die(_("diff_setup_done failed")); diff_tree_sha1(head, new_head, "", &opts); @@ -903,7 +901,7 @@ static int finish_automerge(struct commit_list *common, strbuf_addch(&merge_msg, '\n'); run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); + strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(result_commit, buf.buf); strbuf_release(&buf); drop_save(); @@ -1033,10 +1031,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) git_config(git_merge_config, NULL); - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - if (branch_mergeoptions) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, diff --git a/builtin/push.c b/builtin/push.c index 9cebf9ea23..35cce532f2 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -8,6 +8,7 @@ #include "remote.h" #include "transport.h" #include "parse-options.h" +#include "submodule.h" static const char * const push_usage[] = { "git push [<options>] [<repository> [<refspec>...]]", @@ -219,6 +220,21 @@ static int do_push(const char *repo, int flags) return !!errs; } +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + int *flags = opt->value; + if (arg) { + if (!strcmp(arg, "check")) + *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else + die("bad %s argument: %s", opt->long_name, arg); + } else + die("option %s needs an argument (check)", opt->long_name); + + return 0; +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -236,6 +252,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check", + "controls recursive pushing of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 60260d0aa9..ae164da4d5 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -669,7 +669,7 @@ static const char *parse_pack_header(struct pack_header *hdr) static const char *pack_lockfile; -static const char *unpack(int quiet) +static const char *unpack(void) { struct pack_header hdr; const char *hdr_err; @@ -684,10 +684,8 @@ static const char *unpack(int quiet) if (ntohl(hdr.hdr_entries) < unpack_limit) { int code, i = 0; - const char *unpacker[5]; + const char *unpacker[4]; unpacker[i++] = "unpack-objects"; - if (quiet) - unpacker[i++] = "-q"; if (receive_fsck_objects) unpacker[i++] = "--strict"; unpacker[i++] = hdr_arg; @@ -788,7 +786,6 @@ static void add_alternate_refs(void) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { - int quiet = 0; int advertise_refs = 0; int stateless_rpc = 0; int i; @@ -802,11 +799,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *arg = *argv++; if (*arg == '-') { - if (!strcmp(arg, "--quiet")) { - quiet = 1; - continue; - } - if (!strcmp(arg, "--advertise-refs")) { advertise_refs = 1; continue; @@ -855,7 +847,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *unpack_status = NULL; if (!delete_only(commands)) - unpack_status = unpack(quiet); + unpack_status = unpack(); execute_commands(commands, unpack_status); if (pack_lockfile) unlink_or_warn(pack_lockfile); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 40a1675997..c1f6ddd927 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -439,10 +439,6 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.force_update = 1; continue; } - if (!strcmp(arg, "--quiet")) { - args.quiet = 1; - continue; - } if (!strcmp(arg, "--verbose")) { args.verbose = 1; continue; @@ -492,13 +488,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) fd[0] = 0; fd[1] = 1; } else { - struct strbuf sb = STRBUF_INIT; - strbuf_addstr(&sb, receivepack); - if (args.quiet) - strbuf_addstr(&sb, " --quiet"); - conn = git_connect(fd, dest, sb.buf, + conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0); - strbuf_release(&sb); } memset(&extra_have, 0, sizeof(extra_have)); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index facc63a79e..4b480d7c7c 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -26,14 +26,14 @@ static const char **default_arg; static const char *get_color_code(int idx) { - if (showbranch_use_color) + if (want_color(showbranch_use_color)) return column_colors_ansi[idx % column_colors_ansi_max]; return ""; } static const char *get_color_reset_code(void) { - if (showbranch_use_color) + if (want_color(showbranch_use_color)) return GIT_COLOR_RESET; return ""; } @@ -573,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.showbranch")) { - showbranch_use_color = git_config_colorbool(var, value, -1); + showbranch_use_color = git_config_colorbool(var, value); return 0; } @@ -685,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); - if (showbranch_use_color == -1) - showbranch_use_color = git_use_color_default; - /* If nothing is specified, try the default first */ if (ac == 1 && default_num) { ac = default_num; @@ -438,7 +438,7 @@ extern int set_git_dir(const char *path); extern const char *get_git_namespace(void); extern const char *strip_namespace(const char *namespaced_ref); extern const char *get_git_work_tree(void); -extern const char *read_gitfile_gently(const char *path); +extern const char *read_gitfile(const char *path); extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" @@ -1,7 +1,8 @@ #include "cache.h" #include "color.h" -int git_use_color_default = 0; +static int git_use_color_default = 0; +int color_stdout_is_tty = -1; /* * The list of available column colors. @@ -157,7 +158,7 @@ bad: die("bad color value '%.*s' for variable '%s'", value_len, value, var); } -int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) +int git_config_colorbool(const char *var, const char *value) { if (value) { if (!strcasecmp(value, "never")) @@ -165,7 +166,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) if (!strcasecmp(value, "always")) return 1; if (!strcasecmp(value, "auto")) - goto auto_color; + return GIT_COLOR_AUTO; } if (!var) @@ -176,10 +177,14 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) return 0; /* any normal truth value defaults to 'auto' */ - auto_color: - if (stdout_is_tty < 0) - stdout_is_tty = isatty(1); - if (stdout_is_tty || (pager_in_use() && pager_use_color)) { + return GIT_COLOR_AUTO; +} + +static int check_auto_color(void) +{ + if (color_stdout_is_tty < 0) + color_stdout_is_tty = isatty(1); + if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) { char *term = getenv("TERM"); if (term && strcmp(term, "dumb")) return 1; @@ -187,13 +192,36 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) return 0; } -int git_color_default_config(const char *var, const char *value, void *cb) +int want_color(int var) +{ + static int want_auto = -1; + + if (var < 0) + var = git_use_color_default; + + if (var == GIT_COLOR_AUTO) { + if (want_auto < 0) + want_auto = check_auto_color(); + return want_auto; + } + return var; +} + +int git_color_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "color.ui")) { - git_use_color_default = git_config_colorbool(var, value, -1); + git_use_color_default = git_config_colorbool(var, value); return 0; } + return 0; +} + +int git_color_default_config(const char *var, const char *value, void *cb) +{ + if (git_color_config(var, value, cb) < 0) + return -1; + return git_default_config(var, value, cb); } @@ -49,20 +49,34 @@ struct strbuf; #define GIT_COLOR_NIL "NIL" /* - * This variable stores the value of color.ui + * The first three are chosen to match common usage in the code, and what is + * returned from git_config_colorbool. The "auto" value can be returned from + * config_colorbool, and will be converted by want_color() into either 0 or 1. */ -extern int git_use_color_default; +#define GIT_COLOR_UNKNOWN -1 +#define GIT_COLOR_NEVER 0 +#define GIT_COLOR_ALWAYS 1 +#define GIT_COLOR_AUTO 2 /* A default list of colors to use for commit graphs and show-branch output */ extern const char *column_colors_ansi[]; extern const int column_colors_ansi_max; /* - * Use this instead of git_default_config if you need the value of color.ui. + * Generally the color code will lazily figure this out itself, but + * this provides a mechanism for callers to override autodetection. */ +extern int color_stdout_is_tty; + +/* + * Use the first one if you need only color config; the second is a convenience + * if you are just going to change to git_default_config, too. + */ +int git_color_config(const char *var, const char *value, void *cb); int git_color_default_config(const char *var, const char *value, void *cb); -int git_config_colorbool(const char *var, const char *value, int stdout_is_tty); +int git_config_colorbool(const char *var, const char *value); +int want_color(int var); void color_parse(const char *value, const char *var, char *dst); void color_parse_mem(const char *value, int len, const char *var, char *dst); __attribute__((format (printf, 3, 4))) diff --git a/combine-diff.c b/combine-diff.c index b11eb7102c..214014dc64 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -702,9 +702,8 @@ static void show_combined_header(struct combine_diff_path *elem, int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV; const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/"; const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/"; - int use_color = DIFF_OPT_TST(opt, COLOR_DIFF); - const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); - const char *c_reset = diff_get_color(use_color, DIFF_RESET); + const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO); + const char *c_reset = diff_get_color_opt(opt, DIFF_RESET); const char *abb; int added = 0; int deleted = 0; @@ -964,7 +963,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, show_combined_header(elem, num_parent, dense, rev, mode_differs, 1); dump_sline(sline, cnt, num_parent, - DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted); + opt->use_color, result_deleted); } free(result); @@ -1050,6 +1049,72 @@ void show_combined_diff(struct combine_diff_path *p, show_patch_diff(p, num_parent, dense, 1, rev); } +static void free_combined_pair(struct diff_filepair *pair) +{ + free(pair->two); + free(pair); +} + +/* + * A combine_diff_path expresses N parents on the LHS against 1 merge + * result. Synthesize a diff_filepair that has N entries on the "one" + * side and 1 entry on the "two" side. + * + * In the future, we might want to add more data to combine_diff_path + * so that we can fill fields we are ignoring (most notably, size) here, + * but currently nobody uses it, so this should suffice for now. + */ +static struct diff_filepair *combined_pair(struct combine_diff_path *p, + int num_parent) +{ + int i; + struct diff_filepair *pair; + struct diff_filespec *pool; + + pair = xmalloc(sizeof(*pair)); + pool = xcalloc(num_parent + 1, sizeof(struct diff_filespec)); + pair->one = pool + 1; + pair->two = pool; + + for (i = 0; i < num_parent; i++) { + pair->one[i].path = p->path; + pair->one[i].mode = p->parent[i].mode; + hashcpy(pair->one[i].sha1, p->parent[i].sha1); + pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1); + pair->one[i].has_more_entries = 1; + } + pair->one[num_parent - 1].has_more_entries = 0; + + pair->two->path = p->path; + pair->two->mode = p->mode; + hashcpy(pair->two->sha1, p->sha1); + pair->two->sha1_valid = !is_null_sha1(p->sha1); + return pair; +} + +static void handle_combined_callback(struct diff_options *opt, + struct combine_diff_path *paths, + int num_parent, + int num_paths) +{ + struct combine_diff_path *p; + struct diff_queue_struct q; + int i; + + q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *)); + q.alloc = num_paths; + q.nr = num_paths; + for (i = 0, p = paths; p; p = p->next) { + if (!p->len) + continue; + q.queue[i++] = combined_pair(p, num_parent); + } + opt->format_callback(&q, opt, opt->format_callback_data); + for (i = 0; i < num_paths; i++) + free_combined_pair(q.queue[i]); + free(q.queue); +} + void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, @@ -1109,6 +1174,9 @@ void diff_tree_combined(const unsigned char *sha1, else if (opt->output_format & (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)) needsep = 1; + else if (opt->output_format & DIFF_FORMAT_CALLBACK) + handle_combined_callback(opt, paths, num_parent, num_paths); + if (opt->output_format & DIFF_FORMAT_PATCH) { if (needsep) putchar(opt->line_termination); @@ -214,22 +214,12 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1) return commit_graft[pos]; } -int write_shallow_commits(struct strbuf *out, int use_pack_protocol) -{ - int i, count = 0; - for (i = 0; i < commit_graft_nr; i++) - if (commit_graft[i]->nr_parent < 0) { - const char *hex = - sha1_to_hex(commit_graft[i]->sha1); - count++; - if (use_pack_protocol) - packet_buf_write(out, "shallow %s", hex); - else { - strbuf_addstr(out, hex); - strbuf_addch(out, '\n'); - } - } - return count; +int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data) +{ + int i, ret; + for (i = ret = 0; i < commit_graft_nr && !ret; i++) + ret = fn(commit_graft[i], cb_data); + return ret; } int unregister_shallow(const unsigned char *sha1) @@ -142,6 +142,7 @@ struct commit_graft { int nr_parent; /* < 0 if shallow commit */ unsigned char parent[FLEX_ARRAY][20]; /* more */ }; +typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *); struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); @@ -153,7 +154,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1); -extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol); +extern int for_each_commit_graft(each_commit_graft_fn, void *); extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); diff --git a/compat/obstack.c b/compat/obstack.c new file mode 100644 index 0000000000..a89ab5b8e8 --- /dev/null +++ b/compat/obstack.c @@ -0,0 +1,414 @@ +/* obstack.c - subroutines used implicitly by object stack macros + Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, + 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +#include "git-compat-util.h" +#include <gettext.h> +#include "obstack.h" + +/* NOTE BEFORE MODIFYING THIS FILE: This version number must be + incremented whenever callers compiled using an old obstack.h can no + longer properly call the functions in this obstack.c. */ +#define OBSTACK_INTERFACE_VERSION 1 + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself, and the installed library + supports the same library interface we do. This code is part of the GNU + C Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object + files, it is simpler to just do this in the source for each such file. */ + +#include <stdio.h> /* Random thing to get __GNU_LIBRARY__. */ +#if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1 +# include <gnu-versions.h> +# if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#include <stddef.h> + +#ifndef ELIDE_CODE + + +# if HAVE_INTTYPES_H +# include <inttypes.h> +# endif +# if HAVE_STDINT_H || defined _LIBC +# include <stdint.h> +# endif + +/* Determine default alignment. */ +union fooround +{ + uintmax_t i; + long double d; + void *p; +}; +struct fooalign +{ + char c; + union fooround u; +}; +/* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT. + But in fact it might be less smart and round addresses to as much as + DEFAULT_ROUNDING. So we prepare for it to do that. */ +enum + { + DEFAULT_ALIGNMENT = offsetof (struct fooalign, u), + DEFAULT_ROUNDING = sizeof (union fooround) + }; + +/* When we copy a long block of data, this is the unit to do it with. + On some machines, copying successive ints does not work; + in such a case, redefine COPYING_UNIT to `long' (if that works) + or `char' as a last resort. */ +# ifndef COPYING_UNIT +# define COPYING_UNIT int +# endif + + +/* The functions allocating more room by calling `obstack_chunk_alloc' + jump to the handler pointed to by `obstack_alloc_failed_handler'. + This can be set to a user defined function which should either + abort gracefully or use longjump - but shouldn't return. This + variable by default points to the internal function + `print_and_abort'. */ +static void print_and_abort (void); +void (*obstack_alloc_failed_handler) (void) = print_and_abort; + +# ifdef _LIBC +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4) +/* A looong time ago (before 1994, anyway; we're not sure) this global variable + was used by non-GNU-C macros to avoid multiple evaluation. The GNU C + library still exports it because somebody might use it. */ +struct obstack *_obstack_compat; +compat_symbol (libc, _obstack_compat, _obstack, GLIBC_2_0); +# endif +# endif + +/* Define a macro that either calls functions with the traditional malloc/free + calling interface, or calls functions with the mmalloc/mfree interface + (that adds an extra first argument), based on the state of use_extra_arg. + For free, do not use ?:, since some compilers, like the MIPS compilers, + do not allow (expr) ? void : void. */ + +# define CALL_CHUNKFUN(h, size) \ + (((h) -> use_extra_arg) \ + ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \ + : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size))) + +# define CALL_FREEFUN(h, old_chunk) \ + do { \ + if ((h) -> use_extra_arg) \ + (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \ + else \ + (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \ + } while (0) + + +/* Initialize an obstack H for use. Specify chunk size SIZE (0 means default). + Objects start on multiples of ALIGNMENT (0 means use default). + CHUNKFUN is the function to use to allocate chunks, + and FREEFUN the function to free them. + + Return nonzero if successful, calls obstack_alloc_failed_handler if + allocation fails. */ + +int +_obstack_begin (struct obstack *h, + int size, int alignment, + void *(*chunkfun) (long), + void (*freefun) (void *)) +{ + register struct _obstack_chunk *chunk; /* points to new chunk */ + + if (alignment == 0) + alignment = DEFAULT_ALIGNMENT; + if (size == 0) + /* Default size is what GNU malloc can fit in a 4096-byte block. */ + { + /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. + Use the values for range checking, because if range checking is off, + the extra bytes won't be missed terribly, but if range checking is on + and we used a larger request, a whole extra 4096 bytes would be + allocated. + + These number are irrelevant to the new GNU malloc. I suspect it is + less sensitive to the size of the request. */ + int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + + 4 + DEFAULT_ROUNDING - 1) + & ~(DEFAULT_ROUNDING - 1)); + size = 4096 - extra; + } + + h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun; + h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; + h->chunk_size = size; + h->alignment_mask = alignment - 1; + h->use_extra_arg = 0; + + chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); + if (!chunk) + (*obstack_alloc_failed_handler) (); + h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents, + alignment - 1); + h->chunk_limit = chunk->limit + = (char *) chunk + h->chunk_size; + chunk->prev = 0; + /* The initial chunk now contains no empty object. */ + h->maybe_empty_object = 0; + h->alloc_failed = 0; + return 1; +} + +int +_obstack_begin_1 (struct obstack *h, int size, int alignment, + void *(*chunkfun) (void *, long), + void (*freefun) (void *, void *), + void *arg) +{ + register struct _obstack_chunk *chunk; /* points to new chunk */ + + if (alignment == 0) + alignment = DEFAULT_ALIGNMENT; + if (size == 0) + /* Default size is what GNU malloc can fit in a 4096-byte block. */ + { + /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. + Use the values for range checking, because if range checking is off, + the extra bytes won't be missed terribly, but if range checking is on + and we used a larger request, a whole extra 4096 bytes would be + allocated. + + These number are irrelevant to the new GNU malloc. I suspect it is + less sensitive to the size of the request. */ + int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + + 4 + DEFAULT_ROUNDING - 1) + & ~(DEFAULT_ROUNDING - 1)); + size = 4096 - extra; + } + + h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun; + h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; + h->chunk_size = size; + h->alignment_mask = alignment - 1; + h->extra_arg = arg; + h->use_extra_arg = 1; + + chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); + if (!chunk) + (*obstack_alloc_failed_handler) (); + h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents, + alignment - 1); + h->chunk_limit = chunk->limit + = (char *) chunk + h->chunk_size; + chunk->prev = 0; + /* The initial chunk now contains no empty object. */ + h->maybe_empty_object = 0; + h->alloc_failed = 0; + return 1; +} + +/* Allocate a new current chunk for the obstack *H + on the assumption that LENGTH bytes need to be added + to the current object, or a new object of length LENGTH allocated. + Copies any partial object from the end of the old chunk + to the beginning of the new one. */ + +void +_obstack_newchunk (struct obstack *h, int length) +{ + register struct _obstack_chunk *old_chunk = h->chunk; + register struct _obstack_chunk *new_chunk; + register long new_size; + register long obj_size = h->next_free - h->object_base; + register long i; + long already; + char *object_base; + + /* Compute size for new chunk. */ + new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100; + if (new_size < h->chunk_size) + new_size = h->chunk_size; + + /* Allocate and initialize the new chunk. */ + new_chunk = CALL_CHUNKFUN (h, new_size); + if (!new_chunk) + (*obstack_alloc_failed_handler) (); + h->chunk = new_chunk; + new_chunk->prev = old_chunk; + new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size; + + /* Compute an aligned object_base in the new chunk */ + object_base = + __PTR_ALIGN ((char *) new_chunk, new_chunk->contents, h->alignment_mask); + + /* Move the existing object to the new chunk. + Word at a time is fast and is safe if the object + is sufficiently aligned. */ + if (h->alignment_mask + 1 >= DEFAULT_ALIGNMENT) + { + for (i = obj_size / sizeof (COPYING_UNIT) - 1; + i >= 0; i--) + ((COPYING_UNIT *)object_base)[i] + = ((COPYING_UNIT *)h->object_base)[i]; + /* We used to copy the odd few remaining bytes as one extra COPYING_UNIT, + but that can cross a page boundary on a machine + which does not do strict alignment for COPYING_UNITS. */ + already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT); + } + else + already = 0; + /* Copy remaining bytes one by one. */ + for (i = already; i < obj_size; i++) + object_base[i] = h->object_base[i]; + + /* If the object just copied was the only data in OLD_CHUNK, + free that chunk and remove it from the chain. + But not if that chunk might contain an empty object. */ + if (! h->maybe_empty_object + && (h->object_base + == __PTR_ALIGN ((char *) old_chunk, old_chunk->contents, + h->alignment_mask))) + { + new_chunk->prev = old_chunk->prev; + CALL_FREEFUN (h, old_chunk); + } + + h->object_base = object_base; + h->next_free = h->object_base + obj_size; + /* The new chunk certainly contains no empty object yet. */ + h->maybe_empty_object = 0; +} +# ifdef _LIBC +libc_hidden_def (_obstack_newchunk) +# endif + +/* Return nonzero if object OBJ has been allocated from obstack H. + This is here for debugging. + If you use it in a program, you are probably losing. */ + +/* Suppress -Wmissing-prototypes warning. We don't want to declare this in + obstack.h because it is just for debugging. */ +int _obstack_allocated_p (struct obstack *h, void *obj); + +int +_obstack_allocated_p (struct obstack *h, void *obj) +{ + register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ + register struct _obstack_chunk *plp; /* point to previous chunk if any */ + + lp = (h)->chunk; + /* We use >= rather than > since the object cannot be exactly at + the beginning of the chunk but might be an empty object exactly + at the end of an adjacent chunk. */ + while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + { + plp = lp->prev; + lp = plp; + } + return lp != 0; +} + +/* Free objects in obstack H, including OBJ and everything allocate + more recently than OBJ. If OBJ is zero, free everything in H. */ + +# undef obstack_free + +void +obstack_free (struct obstack *h, void *obj) +{ + register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ + register struct _obstack_chunk *plp; /* point to previous chunk if any */ + + lp = h->chunk; + /* We use >= because there cannot be an object at the beginning of a chunk. + But there can be an empty object at that address + at the end of another chunk. */ + while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + { + plp = lp->prev; + CALL_FREEFUN (h, lp); + lp = plp; + /* If we switch chunks, we can't tell whether the new current + chunk contains an empty object, so assume that it may. */ + h->maybe_empty_object = 1; + } + if (lp) + { + h->object_base = h->next_free = (char *) (obj); + h->chunk_limit = lp->limit; + h->chunk = lp; + } + else if (obj != 0) + /* obj is not in any of the chunks! */ + abort (); +} + +# ifdef _LIBC +/* Older versions of libc used a function _obstack_free intended to be + called by non-GCC compilers. */ +strong_alias (obstack_free, _obstack_free) +# endif + +int +_obstack_memory_used (struct obstack *h) +{ + register struct _obstack_chunk* lp; + register int nbytes = 0; + + for (lp = h->chunk; lp != 0; lp = lp->prev) + { + nbytes += lp->limit - (char *) lp; + } + return nbytes; +} + +# ifdef _LIBC +# include <libio/iolibio.h> +# endif + +# ifndef __attribute__ +/* This feature is available in gcc versions 2.5 and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) +# define __attribute__(Spec) /* empty */ +# endif +# endif + +static void +__attribute__ ((noreturn)) +print_and_abort (void) +{ + /* Don't change any of these strings. Yes, it would be possible to add + the newline to the string and use fputs or so. But this must not + happen because the "memory exhausted" message appears in other places + like this and the translation should be reused instead of creating + a very similar string which requires a separate translation. */ +# ifdef _LIBC + (void) __fxprintf (NULL, "%s\n", _("memory exhausted")); +# else + fprintf (stderr, "%s\n", _("memory exhausted")); +# endif + exit (1); +} + +#endif /* !ELIDE_CODE */ diff --git a/compat/obstack.h b/compat/obstack.h new file mode 100644 index 0000000000..d178bd6716 --- /dev/null +++ b/compat/obstack.h @@ -0,0 +1,506 @@ +/* obstack.h - object stack macros + Copyright (C) 1988-1994,1996-1999,2003,2004,2005,2009 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* Summary: + +All the apparent functions defined here are macros. The idea +is that you would use these pre-tested macros to solve a +very specific set of problems, and they would run fast. +Caution: no side-effects in arguments please!! They may be +evaluated MANY times!! + +These macros operate a stack of objects. Each object starts life +small, and may grow to maturity. (Consider building a word syllable +by syllable.) An object can move while it is growing. Once it has +been "finished" it never changes address again. So the "top of the +stack" is typically an immature growing object, while the rest of the +stack is of mature, fixed size and fixed address objects. + +These routines grab large chunks of memory, using a function you +supply, called `obstack_chunk_alloc'. On occasion, they free chunks, +by calling `obstack_chunk_free'. You must define them and declare +them before using any obstack macros. + +Each independent stack is represented by a `struct obstack'. +Each of the obstack macros expects a pointer to such a structure +as the first argument. + +One motivation for this package is the problem of growing char strings +in symbol tables. Unless you are "fascist pig with a read-only mind" +--Gosper's immortal quote from HAKMEM item 154, out of context--you +would not like to put any arbitrary upper limit on the length of your +symbols. + +In practice this often means you will build many short symbols and a +few long symbols. At the time you are reading a symbol you don't know +how long it is. One traditional method is to read a symbol into a +buffer, realloc()ating the buffer every time you try to read a symbol +that is longer than the buffer. This is beaut, but you still will +want to copy the symbol from the buffer to a more permanent +symbol-table entry say about half the time. + +With obstacks, you can work differently. Use one obstack for all symbol +names. As you read a symbol, grow the name in the obstack gradually. +When the name is complete, finalize it. Then, if the symbol exists already, +free the newly read name. + +The way we do this is to take a large chunk, allocating memory from +low addresses. When you want to build a symbol in the chunk you just +add chars above the current "high water mark" in the chunk. When you +have finished adding chars, because you got to the end of the symbol, +you know how long the chars are, and you can create a new object. +Mostly the chars will not burst over the highest address of the chunk, +because you would typically expect a chunk to be (say) 100 times as +long as an average object. + +In case that isn't clear, when we have enough chars to make up +the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed) +so we just point to it where it lies. No moving of chars is +needed and this is the second win: potentially long strings need +never be explicitly shuffled. Once an object is formed, it does not +change its address during its lifetime. + +When the chars burst over a chunk boundary, we allocate a larger +chunk, and then copy the partly formed object from the end of the old +chunk to the beginning of the new larger chunk. We then carry on +accreting characters to the end of the object as we normally would. + +A special macro is provided to add a single char at a time to a +growing object. This allows the use of register variables, which +break the ordinary 'growth' macro. + +Summary: + We allocate large chunks. + We carve out one object at a time from the current chunk. + Once carved, an object never moves. + We are free to append data of any size to the currently + growing object. + Exactly one object is growing in an obstack at any one time. + You can run one obstack per control block. + You may have as many control blocks as you dare. + Because of the way we do it, you can `unwind' an obstack + back to a previous state. (You may remove objects much + as you would with a stack.) +*/ + + +/* Don't do the contents of this file more than once. */ + +#ifndef _OBSTACK_H +#define _OBSTACK_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* We need the type of a pointer subtraction. If __PTRDIFF_TYPE__ is + defined, as with GNU C, use that; that way we don't pollute the + namespace with <stddef.h>'s symbols. Otherwise, include <stddef.h> + and use ptrdiff_t. */ + +#ifdef __PTRDIFF_TYPE__ +# define PTR_INT_TYPE __PTRDIFF_TYPE__ +#else +# include <stddef.h> +# define PTR_INT_TYPE ptrdiff_t +#endif + +/* If B is the base of an object addressed by P, return the result of + aligning P to the next multiple of A + 1. B and P must be of type + char *. A + 1 must be a power of 2. */ + +#define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A))) + +/* Similiar to _BPTR_ALIGN (B, P, A), except optimize the common case + where pointers can be converted to integers, aligned as integers, + and converted back again. If PTR_INT_TYPE is narrower than a + pointer (e.g., the AS/400), play it safe and compute the alignment + relative to B. Otherwise, use the faster strategy of computing the + alignment relative to 0. */ + +#define __PTR_ALIGN(B, P, A) \ + __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \ + P, A) + +#include <string.h> + +struct _obstack_chunk /* Lives at front of each chunk. */ +{ + char *limit; /* 1 past end of this chunk */ + struct _obstack_chunk *prev; /* address of prior chunk or NULL */ + char contents[4]; /* objects begin here */ +}; + +struct obstack /* control current object in current chunk */ +{ + long chunk_size; /* preferred size to allocate chunks in */ + struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */ + char *object_base; /* address of object we are building */ + char *next_free; /* where to add next char to current object */ + char *chunk_limit; /* address of char after current chunk */ + union + { + PTR_INT_TYPE tempint; + void *tempptr; + } temp; /* Temporary for some macros. */ + int alignment_mask; /* Mask of alignment for each object. */ + /* These prototypes vary based on `use_extra_arg', and we use + casts to the prototypeless function type in all assignments, + but having prototypes here quiets -Wstrict-prototypes. */ + struct _obstack_chunk *(*chunkfun) (void *, long); + void (*freefun) (void *, struct _obstack_chunk *); + void *extra_arg; /* first arg for chunk alloc/dealloc funcs */ + unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */ + unsigned maybe_empty_object:1;/* There is a possibility that the current + chunk contains a zero-length object. This + prevents freeing the chunk if we allocate + a bigger chunk to replace it. */ + unsigned alloc_failed:1; /* No longer used, as we now call the failed + handler on error, but retained for binary + compatibility. */ +}; + +/* Declare the external functions we use; they are in obstack.c. */ + +extern void _obstack_newchunk (struct obstack *, int); +extern int _obstack_begin (struct obstack *, int, int, + void *(*) (long), void (*) (void *)); +extern int _obstack_begin_1 (struct obstack *, int, int, + void *(*) (void *, long), + void (*) (void *, void *), void *); +extern int _obstack_memory_used (struct obstack *); + +void obstack_free (struct obstack *, void *); + + +/* Error handler called when `obstack_chunk_alloc' failed to allocate + more memory. This can be set to a user defined function which + should either abort gracefully or use longjump - but shouldn't + return. The default action is to print a message and abort. */ +extern void (*obstack_alloc_failed_handler) (void); + +/* Pointer to beginning of object being allocated or to be allocated next. + Note that this might not be the final address of the object + because a new chunk might be needed to hold the final size. */ + +#define obstack_base(h) ((void *) (h)->object_base) + +/* Size for allocating ordinary chunks. */ + +#define obstack_chunk_size(h) ((h)->chunk_size) + +/* Pointer to next byte not yet allocated in current chunk. */ + +#define obstack_next_free(h) ((h)->next_free) + +/* Mask specifying low bits that should be clear in address of an object. */ + +#define obstack_alignment_mask(h) ((h)->alignment_mask) + +/* To prevent prototype warnings provide complete argument list. */ +#define obstack_init(h) \ + _obstack_begin ((h), 0, 0, \ + (void *(*) (long)) obstack_chunk_alloc, \ + (void (*) (void *)) obstack_chunk_free) + +#define obstack_begin(h, size) \ + _obstack_begin ((h), (size), 0, \ + (void *(*) (long)) obstack_chunk_alloc, \ + (void (*) (void *)) obstack_chunk_free) + +#define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \ + _obstack_begin ((h), (size), (alignment), \ + (void *(*) (long)) (chunkfun), \ + (void (*) (void *)) (freefun)) + +#define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \ + _obstack_begin_1 ((h), (size), (alignment), \ + (void *(*) (void *, long)) (chunkfun), \ + (void (*) (void *, void *)) (freefun), (arg)) + +#define obstack_chunkfun(h, newchunkfun) \ + ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun)) + +#define obstack_freefun(h, newfreefun) \ + ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun)) + +#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar)) + +#define obstack_blank_fast(h,n) ((h)->next_free += (n)) + +#define obstack_memory_used(h) _obstack_memory_used (h) + +#if defined __GNUC__ && defined __STDC__ && __STDC__ +/* NextStep 2.0 cc is really gcc 1.93 but it defines __GNUC__ = 2 and + does not implement __extension__. But that compiler doesn't define + __GNUC_MINOR__. */ +# if __GNUC__ < 2 || (__NeXT__ && !__GNUC_MINOR__) +# define __extension__ +# endif + +/* For GNU C, if not -traditional, + we can define these macros to compute all args only once + without using a global variable. + Also, we can avoid using the `temp' slot, to make faster code. */ + +# define obstack_object_size(OBSTACK) \ + __extension__ \ + ({ struct obstack const *__o = (OBSTACK); \ + (unsigned) (__o->next_free - __o->object_base); }) + +# define obstack_room(OBSTACK) \ + __extension__ \ + ({ struct obstack const *__o = (OBSTACK); \ + (unsigned) (__o->chunk_limit - __o->next_free); }) + +# define obstack_make_room(OBSTACK,length) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + int __len = (length); \ + if (__o->chunk_limit - __o->next_free < __len) \ + _obstack_newchunk (__o, __len); \ + (void) 0; }) + +# define obstack_empty_p(OBSTACK) \ + __extension__ \ + ({ struct obstack const *__o = (OBSTACK); \ + (__o->chunk->prev == 0 \ + && __o->next_free == __PTR_ALIGN ((char *) __o->chunk, \ + __o->chunk->contents, \ + __o->alignment_mask)); }) + +# define obstack_grow(OBSTACK,where,length) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + int __len = (length); \ + if (__o->next_free + __len > __o->chunk_limit) \ + _obstack_newchunk (__o, __len); \ + memcpy (__o->next_free, where, __len); \ + __o->next_free += __len; \ + (void) 0; }) + +# define obstack_grow0(OBSTACK,where,length) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + int __len = (length); \ + if (__o->next_free + __len + 1 > __o->chunk_limit) \ + _obstack_newchunk (__o, __len + 1); \ + memcpy (__o->next_free, where, __len); \ + __o->next_free += __len; \ + *(__o->next_free)++ = 0; \ + (void) 0; }) + +# define obstack_1grow(OBSTACK,datum) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + if (__o->next_free + 1 > __o->chunk_limit) \ + _obstack_newchunk (__o, 1); \ + obstack_1grow_fast (__o, datum); \ + (void) 0; }) + +/* These assume that the obstack alignment is good enough for pointers + or ints, and that the data added so far to the current object + shares that much alignment. */ + +# define obstack_ptr_grow(OBSTACK,datum) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + if (__o->next_free + sizeof (void *) > __o->chunk_limit) \ + _obstack_newchunk (__o, sizeof (void *)); \ + obstack_ptr_grow_fast (__o, datum); }) \ + +# define obstack_int_grow(OBSTACK,datum) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + if (__o->next_free + sizeof (int) > __o->chunk_limit) \ + _obstack_newchunk (__o, sizeof (int)); \ + obstack_int_grow_fast (__o, datum); }) + +# define obstack_ptr_grow_fast(OBSTACK,aptr) \ +__extension__ \ +({ struct obstack *__o1 = (OBSTACK); \ + *(const void **) __o1->next_free = (aptr); \ + __o1->next_free += sizeof (const void *); \ + (void) 0; }) + +# define obstack_int_grow_fast(OBSTACK,aint) \ +__extension__ \ +({ struct obstack *__o1 = (OBSTACK); \ + *(int *) __o1->next_free = (aint); \ + __o1->next_free += sizeof (int); \ + (void) 0; }) + +# define obstack_blank(OBSTACK,length) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + int __len = (length); \ + if (__o->chunk_limit - __o->next_free < __len) \ + _obstack_newchunk (__o, __len); \ + obstack_blank_fast (__o, __len); \ + (void) 0; }) + +# define obstack_alloc(OBSTACK,length) \ +__extension__ \ +({ struct obstack *__h = (OBSTACK); \ + obstack_blank (__h, (length)); \ + obstack_finish (__h); }) + +# define obstack_copy(OBSTACK,where,length) \ +__extension__ \ +({ struct obstack *__h = (OBSTACK); \ + obstack_grow (__h, (where), (length)); \ + obstack_finish (__h); }) + +# define obstack_copy0(OBSTACK,where,length) \ +__extension__ \ +({ struct obstack *__h = (OBSTACK); \ + obstack_grow0 (__h, (where), (length)); \ + obstack_finish (__h); }) + +/* The local variable is named __o1 to avoid a name conflict + when obstack_blank is called. */ +# define obstack_finish(OBSTACK) \ +__extension__ \ +({ struct obstack *__o1 = (OBSTACK); \ + void *__value = (void *) __o1->object_base; \ + if (__o1->next_free == __value) \ + __o1->maybe_empty_object = 1; \ + __o1->next_free \ + = __PTR_ALIGN (__o1->object_base, __o1->next_free, \ + __o1->alignment_mask); \ + if (__o1->next_free - (char *)__o1->chunk \ + > __o1->chunk_limit - (char *)__o1->chunk) \ + __o1->next_free = __o1->chunk_limit; \ + __o1->object_base = __o1->next_free; \ + __value; }) + +# define obstack_free(OBSTACK, OBJ) \ +__extension__ \ +({ struct obstack *__o = (OBSTACK); \ + void *__obj = (OBJ); \ + if (__obj > (void *)__o->chunk && __obj < (void *)__o->chunk_limit) \ + __o->next_free = __o->object_base = (char *)__obj; \ + else (obstack_free) (__o, __obj); }) + +#else /* not __GNUC__ or not __STDC__ */ + +# define obstack_object_size(h) \ + (unsigned) ((h)->next_free - (h)->object_base) + +# define obstack_room(h) \ + (unsigned) ((h)->chunk_limit - (h)->next_free) + +# define obstack_empty_p(h) \ + ((h)->chunk->prev == 0 \ + && (h)->next_free == __PTR_ALIGN ((char *) (h)->chunk, \ + (h)->chunk->contents, \ + (h)->alignment_mask)) + +/* Note that the call to _obstack_newchunk is enclosed in (..., 0) + so that we can avoid having void expressions + in the arms of the conditional expression. + Casting the third operand to void was tried before, + but some compilers won't accept it. */ + +# define obstack_make_room(h,length) \ +( (h)->temp.tempint = (length), \ + (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0)) + +# define obstack_grow(h,where,length) \ +( (h)->temp.tempint = (length), \ + (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \ + memcpy ((h)->next_free, where, (h)->temp.tempint), \ + (h)->next_free += (h)->temp.tempint) + +# define obstack_grow0(h,where,length) \ +( (h)->temp.tempint = (length), \ + (((h)->next_free + (h)->temp.tempint + 1 > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), (h)->temp.tempint + 1), 0) : 0), \ + memcpy ((h)->next_free, where, (h)->temp.tempint), \ + (h)->next_free += (h)->temp.tempint, \ + *((h)->next_free)++ = 0) + +# define obstack_1grow(h,datum) \ +( (((h)->next_free + 1 > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), 1), 0) : 0), \ + obstack_1grow_fast (h, datum)) + +# define obstack_ptr_grow(h,datum) \ +( (((h)->next_free + sizeof (char *) > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0), \ + obstack_ptr_grow_fast (h, datum)) + +# define obstack_int_grow(h,datum) \ +( (((h)->next_free + sizeof (int) > (h)->chunk_limit) \ + ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0), \ + obstack_int_grow_fast (h, datum)) + +# define obstack_ptr_grow_fast(h,aptr) \ + (((const void **) ((h)->next_free += sizeof (void *)))[-1] = (aptr)) + +# define obstack_int_grow_fast(h,aint) \ + (((int *) ((h)->next_free += sizeof (int)))[-1] = (aint)) + +# define obstack_blank(h,length) \ +( (h)->temp.tempint = (length), \ + (((h)->chunk_limit - (h)->next_free < (h)->temp.tempint) \ + ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \ + obstack_blank_fast (h, (h)->temp.tempint)) + +# define obstack_alloc(h,length) \ + (obstack_blank ((h), (length)), obstack_finish ((h))) + +# define obstack_copy(h,where,length) \ + (obstack_grow ((h), (where), (length)), obstack_finish ((h))) + +# define obstack_copy0(h,where,length) \ + (obstack_grow0 ((h), (where), (length)), obstack_finish ((h))) + +# define obstack_finish(h) \ +( ((h)->next_free == (h)->object_base \ + ? (((h)->maybe_empty_object = 1), 0) \ + : 0), \ + (h)->temp.tempptr = (h)->object_base, \ + (h)->next_free \ + = __PTR_ALIGN ((h)->object_base, (h)->next_free, \ + (h)->alignment_mask), \ + (((h)->next_free - (char *) (h)->chunk \ + > (h)->chunk_limit - (char *) (h)->chunk) \ + ? ((h)->next_free = (h)->chunk_limit) : 0), \ + (h)->object_base = (h)->next_free, \ + (h)->temp.tempptr) + +# define obstack_free(h,obj) \ +( (h)->temp.tempint = (char *) (obj) - (char *) (h)->chunk, \ + ((((h)->temp.tempint > 0 \ + && (h)->temp.tempint < (h)->chunk_limit - (char *) (h)->chunk)) \ + ? (int) ((h)->next_free = (h)->object_base \ + = (h)->temp.tempint + (char *) (h)->chunk) \ + : (((obstack_free) ((h), (h)->temp.tempint + (char *) (h)->chunk), 0), 0))) + +#endif /* not __GNUC__ or not __STDC__ */ + +#ifdef __cplusplus +} /* C++ */ +#endif + +#endif /* obstack.h */ diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 6b9de9e7e0..2f7b270566 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -342,6 +342,11 @@ def gitConfig(key, args = None): # set args to "--bool", for instance _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip() return _gitConfig[key] +def gitConfigList(key): + if not _gitConfig.has_key(key): + _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep) + return _gitConfig[key] + def p4BranchesInGit(branchesAreInRemotes = True): branches = {} @@ -774,17 +779,22 @@ class P4Submit(Command, P4UserMap): if not self.detectRenames: # If not explicitly set check the config variable - self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true" + self.detectRenames = gitConfig("git-p4.detectRenames") - if self.detectRenames: + if self.detectRenames.lower() == "false" or self.detectRenames == "": + diffOpts = "" + elif self.detectRenames.lower() == "true": diffOpts = "-M" else: - diffOpts = "" + diffOpts = "-M%s" % self.detectRenames - if gitConfig("git-p4.detectCopies").lower() == "true": + detectCopies = gitConfig("git-p4.detectCopies") + if detectCopies.lower() == "true": diffOpts += " -C" + elif detectCopies != "" and detectCopies.lower() != "false": + diffOpts += " -C%s" % detectCopies - if gitConfig("git-p4.detectCopiesHarder").lower() == "true": + if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true": diffOpts += " --find-copies-harder" diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id)) @@ -1450,7 +1460,13 @@ class P4Sync(Command, P4UserMap): def getBranchMapping(self): lostAndFoundBranches = set() - for info in p4CmdList("branches"): + user = gitConfig("git-p4.branchUser") + if len(user) > 0: + command = "branches -u %s" % user + else: + command = "branches" + + for info in p4CmdList(command): details = p4Cmd("branch -o %s" % info["branch"]) viewIdx = 0 while details.has_key("View%s" % viewIdx): @@ -1479,6 +1495,25 @@ class P4Sync(Command, P4UserMap): if source not in self.knownBranches: lostAndFoundBranches.add(source) + # Perforce does not strictly require branches to be defined, so we also + # check git config for a branch list. + # + # Example of branch definition in git config file: + # [git-p4] + # branchList=main:branchA + # branchList=main:branchB + # branchList=branchA:branchC + configBranches = gitConfigList("git-p4.branchList") + for branch in configBranches: + if branch: + (source, destination) = branch.split(":") + self.knownBranches[destination] = source + + lostAndFoundBranches.discard(destination) + + if source not in self.knownBranches: + lostAndFoundBranches.add(source) + for branch in lostAndFoundBranches: self.knownBranches[branch] = branch @@ -1824,12 +1859,14 @@ class P4Sync(Command, P4UserMap): else: paths = [] for (prev, cur) in zip(self.previousDepotPaths, depotPaths): - for i in range(0, min(len(cur), len(prev))): - if cur[i] <> prev[i]: + prev_list = prev.split("/") + cur_list = cur.split("/") + for i in range(0, min(len(cur_list), len(prev_list))): + if cur_list[i] <> prev_list[i]: i = i - 1 break - paths.append (cur[:i + 1]) + paths.append ("/".join(cur_list[:i + 1])) self.previousDepotPaths = paths diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt index caa4bb3e30..52003ae904 100644 --- a/contrib/fast-import/git-p4.txt +++ b/contrib/fast-import/git-p4.txt @@ -232,6 +232,44 @@ git-p4.skipUserNameCheck When submitting, git-p4 checks that the git commits are authored by the current p4 user, and warns if they are not. This disables the check. +git-p4.detectRenames + +Detect renames when submitting changes to Perforce server. Will enable -M git +argument. Can be optionally set to a number representing the threshold +percentage value of the rename detection. + + git config [--global] git-p4.detectRenames true + git config [--global] git-p4.detectRenames 50 + +git-p4.detectCopies + +Detect copies when submitting changes to Perforce server. Will enable -C git +argument. Can be optionally set to a number representing the threshold +percentage value of the copy detection. + + git config [--global] git-p4.detectCopies true + git config [--global] git-p4.detectCopies 80 + +git-p4.detectCopiesHarder + +Detect copies even between files that did not change when submitting changes to +Perforce server. Will enable --find-copies-harder git argument. + + git config [--global] git-p4.detectCopies true + +git-p4.branchUser + +Only use branch specifications defined by the selected username. + + git config [--global] git-p4.branchUser username + +git-p4.branchList + +List of branches to be imported when branch detection is enabled. + + git config [--global] git-p4.branchList main:branchA + git config [--global] --add git-p4.branchList main:branchB + Implementation Details... ========================= diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index fa6d41a1ab..ba077c13f9 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -11,11 +11,11 @@ # will have put this somewhere standard. You should make this script # executable then link to it in the repository you would like to use it in. # For example, on debian the hook is stored in -# /usr/share/doc/git-core/contrib/hooks/post-receive-email: +# /usr/share/git-core/contrib/hooks/post-receive-email: # # chmod a+x post-receive-email # cd /path/to/your/repository.git -# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive +# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive # # This hook script assumes it is enabled on the central repository of a # project, with all users pushing only to it and not between each other. It @@ -533,7 +533,7 @@ static int ident_to_git(const char *path, const char *src, size_t len, dollar = memchr(src, '$', len); if (!dollar) break; - memcpy(dst, src, dollar + 1 - src); + memmove(dst, src, dollar + 1 - src); dst += dollar + 1 - src; len -= dollar + 1 - src; src = dollar + 1; @@ -553,7 +553,7 @@ static int ident_to_git(const char *path, const char *src, size_t len, src = dollar + 1; } } - memcpy(dst, src, len); + memmove(dst, src, len); strbuf_setlen(buf, dst + len - buf->buf); return 1; } diff --git a/diff-lib.c b/diff-lib.c index f8454dd291..ebe751e72d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -468,6 +468,7 @@ static int diff_cache(struct rev_info *revs, opts.unpack_data = revs; opts.src_index = &the_index; opts.dst_index = NULL; + opts.pathspec = &revs->diffopt.pathspec; init_tree_desc(&t, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); @@ -137,7 +137,7 @@ static int git_config_rename(const char *var, const char *value) int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { - diff_use_color_default = git_config_colorbool(var, value, -1); + diff_use_color_default = git_config_colorbool(var, value); return 0; } if (!strcmp(var, "diff.renames")) { @@ -164,6 +164,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) if (!strcmp(var, "diff.ignoresubmodules")) handle_ignore_submodules_arg(&default_diff_options, value); + if (git_color_config(var, value, cb) < 0) + return -1; + return git_diff_basic_config(var, value, cb); } @@ -212,7 +215,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) if (!prefixcmp(var, "submodule.")) return parse_submodule_config_option(var, value); - return git_color_default_config(var, value, cb); + return git_default_config(var, value, cb); } static char *quote_two(const char *one, const char *two) @@ -583,11 +586,10 @@ static void emit_rewrite_diff(const char *name_a, struct diff_options *o) { int lc_a, lc_b; - int color_diff = DIFF_OPT_TST(o, COLOR_DIFF); const char *name_a_tab, *name_b_tab; - const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); - const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *reset = diff_get_color(color_diff, DIFF_RESET); + const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO); + const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); + const char *reset = diff_get_color(o->use_color, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; char *data_one, *data_two; @@ -623,7 +625,7 @@ static void emit_rewrite_diff(const char *name_a, size_two = fill_textconv(textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); - ecbdata.color_diff = color_diff; + ecbdata.color_diff = want_color(o->use_color); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); ecbdata.opt = o; @@ -1004,7 +1006,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata) const char *diff_get_color(int diff_use_color, enum color_diff ix) { - if (diff_use_color) + if (want_color(diff_use_color)) return diff_colors[ix]; return ""; } @@ -1808,11 +1810,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; - int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF); int marker_size = data->conflict_marker_size; - const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE); - const char *reset = diff_get_color(color_diff, DIFF_RESET); - const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); + const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE); + const char *reset = diff_get_color(data->o->use_color, DIFF_RESET); + const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW); char *err; char *line_prefix = ""; struct strbuf *msgbuf; @@ -2157,7 +2158,7 @@ static void builtin_diff(const char *name_a, memset(&xecfg, 0, sizeof(xecfg)); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.label_path = lbl; - ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); + ecbdata.color_diff = want_color(o->use_color); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) @@ -2205,7 +2206,7 @@ static void builtin_diff(const char *name_a, break; } } - if (DIFF_OPT_TST(o, COLOR_DIFF)) { + if (want_color(o->use_color)) { struct diff_words_style *st = ecbdata.diff_words->style; st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD); st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW); @@ -2855,7 +2856,7 @@ static void run_diff_cmd(const char *pgm, */ fill_metainfo(msg, name, other, one, two, o, p, &must_show_header, - DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + want_color(o->use_color) && !pgm); xfrm_msg = msg->len ? msg->buf : NULL; } @@ -3021,8 +3022,7 @@ void diff_setup(struct diff_options *options) options->change = diff_change; options->add_remove = diff_addremove; - if (diff_use_color_default > 0) - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; if (diff_no_prefix) { @@ -3412,24 +3412,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--follow")) DIFF_OPT_SET(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = 1; else if (!prefixcmp(arg, "--color=")) { - int value = git_config_colorbool(NULL, arg+8, -1); - if (value == 0) - DIFF_OPT_CLR(options, COLOR_DIFF); - else if (value > 0) - DIFF_OPT_SET(options, COLOR_DIFF); - else + int value = git_config_colorbool(NULL, arg+8); + if (value < 0) return error("option `color' expects \"always\", \"auto\", or \"never\""); + options->use_color = value; } else if (!strcmp(arg, "--no-color")) - DIFF_OPT_CLR(options, COLOR_DIFF); + options->use_color = 0; else if (!strcmp(arg, "--color-words")) { - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } else if (!prefixcmp(arg, "--color-words=")) { - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; options->word_regex = arg + 14; } @@ -3442,7 +3439,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) if (!strcmp(type, "plain")) options->word_diff = DIFF_WORDS_PLAIN; else if (!strcmp(type, "color")) { - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } else if (!strcmp(type, "porcelain")) @@ -58,7 +58,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_SILENT_ON_REMOVE (1 << 5) #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) #define DIFF_OPT_FOLLOW_RENAMES (1 << 7) -#define DIFF_OPT_COLOR_DIFF (1 << 8) +/* (1 << 8) unused */ /* (1 << 9) unused */ #define DIFF_OPT_HAS_CHANGES (1 << 10) #define DIFF_OPT_QUICK (1 << 11) @@ -101,6 +101,7 @@ struct diff_options { const char *single_follow; const char *a_prefix, *b_prefix; unsigned flags; + int use_color; int context; int interhunkcontext; int break_opt; @@ -160,7 +161,7 @@ enum color_diff { }; const char *diff_get_color(int diff_use_color, enum color_diff ix); #define diff_get_color_opt(o, ix) \ - diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix) + diff_get_color((o)->use_color, ix) extern const char mime_boundary_leader[]; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index ea03b9107e..c3760cfefd 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -6,6 +6,7 @@ #include "diff.h" #include "diffcore.h" #include "xdiff-interface.h" +#include "kwset.h" struct diffgrep_cb { regex_t *regexp; @@ -146,7 +147,7 @@ static void diffcore_pickaxe_grep(struct diff_options *o) static unsigned int contains(struct diff_filespec *one, const char *needle, unsigned long len, - regex_t *regexp) + regex_t *regexp, kwset_t kws) { unsigned int cnt; unsigned long sz; @@ -175,9 +176,12 @@ static unsigned int contains(struct diff_filespec *one, } else { /* Classic exact string match */ while (sz) { - const char *found = memmem(data, sz, needle, len); - if (!found) + size_t offset = kwsexec(kws, data, sz, NULL); + const char *found; + if (offset == -1) break; + else + found = data + offset; sz -= found - data + len; data = found + len; cnt++; @@ -195,6 +199,7 @@ static void diffcore_pickaxe_count(struct diff_options *o) unsigned long len = strlen(needle); int i, has_changes; regex_t regex, *regexp = NULL; + kwset_t kws = NULL; struct diff_queue_struct outq; DIFF_QUEUE_CLEAR(&outq); @@ -209,6 +214,10 @@ static void diffcore_pickaxe_count(struct diff_options *o) die("invalid pickaxe regex: %s", errbuf); } regexp = ®ex; + } else { + kws = kwsalloc(NULL); + kwsincr(kws, needle, len); + kwsprep(kws); } if (opts & DIFF_PICKAXE_ALL) { @@ -219,16 +228,16 @@ static void diffcore_pickaxe_count(struct diff_options *o) if (!DIFF_FILE_VALID(p->two)) continue; /* ignore unmerged */ /* created */ - if (contains(p->two, needle, len, regexp)) + if (contains(p->two, needle, len, regexp, kws)) has_changes++; } else if (!DIFF_FILE_VALID(p->two)) { - if (contains(p->one, needle, len, regexp)) + if (contains(p->one, needle, len, regexp, kws)) has_changes++; } else if (!diff_unmodified_pair(p) && - contains(p->one, needle, len, regexp) != - contains(p->two, needle, len, regexp)) + contains(p->one, needle, len, regexp, kws) != + contains(p->two, needle, len, regexp, kws)) has_changes++; } if (has_changes) @@ -251,16 +260,17 @@ static void diffcore_pickaxe_count(struct diff_options *o) if (!DIFF_FILE_VALID(p->two)) ; /* ignore unmerged */ /* created */ - else if (contains(p->two, needle, len, regexp)) + else if (contains(p->two, needle, len, regexp, + kws)) has_changes = 1; } else if (!DIFF_FILE_VALID(p->two)) { - if (contains(p->one, needle, len, regexp)) + if (contains(p->one, needle, len, regexp, kws)) has_changes = 1; } else if (!diff_unmodified_pair(p) && - contains(p->one, needle, len, regexp) != - contains(p->two, needle, len, regexp)) + contains(p->one, needle, len, regexp, kws) != + contains(p->two, needle, len, regexp, kws)) has_changes = 1; if (has_changes) @@ -271,6 +281,8 @@ static void diffcore_pickaxe_count(struct diff_options *o) if (opts & DIFF_PICKAXE_REGEX) regfree(®ex); + else + kwsfree(kws); free(q->queue); *q = outq; diff --git a/diffcore.h b/diffcore.h index b8f1fdecf4..8f32b824cd 100644 --- a/diffcore.h +++ b/diffcore.h @@ -45,7 +45,7 @@ struct diff_filespec { unsigned dirty_submodule : 2; /* For submodules: its work tree is dirty */ #define DIRTY_SUBMODULE_UNTRACKED 1 #define DIRTY_SUBMODULE_MODIFIED 2 - + unsigned has_more_entries : 1; /* only appear in combined diff */ struct userdiff_driver *driver; /* data should be considered "binary"; -1 means "don't know yet" */ int is_binary; diff --git a/environment.c b/environment.c index 03d29e8d48..e96edcfebc 100644 --- a/environment.c +++ b/environment.c @@ -117,7 +117,7 @@ static void setup_git_env(void) git_dir = getenv(GIT_DIR_ENVIRONMENT); git_dir = git_dir ? xstrdup(git_dir) : NULL; if (!git_dir) { - git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + git_dir = read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT); git_dir = git_dir ? xstrdup(git_dir) : NULL; } if (!git_dir) diff --git a/fast-import.c b/fast-import.c index 7cc22625e5..742e7da6b8 100644 --- a/fast-import.c +++ b/fast-import.c @@ -170,6 +170,11 @@ Format of STDIN stream: #define DEPTH_BITS 13 #define MAX_DEPTH ((1<<DEPTH_BITS)-1) +/* + * We abuse the setuid bit on directories to mean "do not delta". + */ +#define NO_DELTA S_ISUID + struct object_entry { struct pack_idx_entry idx; struct object_entry *next; @@ -284,6 +289,7 @@ static uintmax_t marks_set_count; static uintmax_t object_count_by_type[1 << TYPE_BITS]; static uintmax_t duplicate_count_by_type[1 << TYPE_BITS]; static uintmax_t delta_count_by_type[1 << TYPE_BITS]; +static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS]; static unsigned long object_count; static unsigned long branch_count; static unsigned long branch_load_count; @@ -1045,6 +1051,7 @@ static int store_object( } if (last && last->data.buf && last->depth < max_depth && dat->len > 20) { + delta_count_attempts_by_type[type]++; delta = diff_delta(last->data.buf, last->data.len, dat->buf, dat->len, &deltalen, dat->len - 20); @@ -1416,8 +1423,9 @@ static void mktree(struct tree_content *t, int v, struct strbuf *b) struct tree_entry *e = t->entries[i]; if (!e->versions[v].mode) continue; - strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode, - e->name->str_dat, '\0'); + strbuf_addf(b, "%o %s%c", + (unsigned int)(e->versions[v].mode & ~NO_DELTA), + e->name->str_dat, '\0'); strbuf_add(b, e->versions[v].sha1, 20); } } @@ -1427,7 +1435,7 @@ static void store_tree(struct tree_entry *root) struct tree_content *t = root->tree; unsigned int i, j, del; struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 }; - struct object_entry *le; + struct object_entry *le = NULL; if (!is_null_sha1(root->versions[1].sha1)) return; @@ -1437,7 +1445,8 @@ static void store_tree(struct tree_entry *root) store_tree(t->entries[i]); } - le = find_object(root->versions[0].sha1); + if (!(root->versions[0].mode & NO_DELTA)) + le = find_object(root->versions[0].sha1); if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { mktree(t, 0, &old_tree); lo.data = old_tree; @@ -1471,6 +1480,7 @@ static void tree_content_replace( { if (!S_ISDIR(mode)) die("Root cannot be a non-directory"); + hashclr(root->versions[0].sha1); hashcpy(root->versions[1].sha1, sha1); if (root->tree) release_tree_content_recursive(root->tree); @@ -1515,6 +1525,23 @@ static int tree_content_set( if (e->tree) release_tree_content_recursive(e->tree); e->tree = subtree; + + /* + * We need to leave e->versions[0].sha1 alone + * to avoid modifying the preimage tree used + * when writing out the parent directory. + * But after replacing the subdir with a + * completely different one, it's not a good + * delta base any more, and besides, we've + * thrown away the tree entries needed to + * make a delta against it. + * + * So let's just explicitly disable deltas + * for the subtree. + */ + if (S_ISDIR(e->versions[0].mode)) + e->versions[0].mode |= NO_DELTA; + hashclr(root->versions[1].sha1); return 1; } @@ -1969,32 +1996,41 @@ static int validate_raw_date(const char *src, char *result, int maxlen) static char *parse_ident(const char *buf) { - const char *gt; + const char *ltgt; size_t name_len; char *ident; - gt = strrchr(buf, '>'); - if (!gt) + /* ensure there is a space delimiter even if there is no name */ + if (*buf == '<') + --buf; + + ltgt = buf + strcspn(buf, "<>"); + if (*ltgt != '<') + die("Missing < in ident string: %s", buf); + if (ltgt != buf && ltgt[-1] != ' ') + die("Missing space before < in ident string: %s", buf); + ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>"); + if (*ltgt != '>') die("Missing > in ident string: %s", buf); - gt++; - if (*gt != ' ') + ltgt++; + if (*ltgt != ' ') die("Missing space after > in ident string: %s", buf); - gt++; - name_len = gt - buf; + ltgt++; + name_len = ltgt - buf; ident = xmalloc(name_len + 24); strncpy(ident, buf, name_len); switch (whenspec) { case WHENSPEC_RAW: - if (validate_raw_date(gt, ident + name_len, 24) < 0) - die("Invalid raw date \"%s\" in ident: %s", gt, buf); + if (validate_raw_date(ltgt, ident + name_len, 24) < 0) + die("Invalid raw date \"%s\" in ident: %s", ltgt, buf); break; case WHENSPEC_RFC2822: - if (parse_date(gt, ident + name_len, 24) < 0) - die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf); + if (parse_date(ltgt, ident + name_len, 24) < 0) + die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf); break; case WHENSPEC_NOW: - if (strcmp("now", gt)) + if (strcmp("now", ltgt)) die("Date in ident must be 'now': %s", buf); datestamp(ident + name_len, 24); break; @@ -2690,13 +2726,13 @@ static void parse_new_tag(void) type = oe->type; hashcpy(sha1, oe->idx.sha1); } else if (!get_sha1(from, sha1)) { - unsigned long size; - char *buf; - - buf = read_sha1_file(sha1, &type, &size); - if (!buf || size < 46) - die("Not a valid commit: %s", from); - free(buf); + struct object_entry *oe = find_object(sha1); + if (!oe) { + type = sha1_object_info(sha1, NULL); + if (type < 0) + die("Not a valid object: %s", from); + } else + type = oe->type; } else die("Invalid ref name or SHA1 expression: %s", from); read_next_command(); @@ -2800,7 +2836,12 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20]) strbuf_release(&line); cat_blob_write(buf, size); cat_blob_write("\n", 1); - free(buf); + if (oe && oe->pack_id == pack_id) { + last_blob.offset = oe->idx.offset; + strbuf_attach(&last_blob.data, buf, size, size); + last_blob.depth = oe->depth; + } else + free(buf); } static void parse_cat_blob(void) @@ -2929,7 +2970,7 @@ static void print_ls(int mode, const unsigned char *sha1, const char *path) /* mode SP type SP object_name TAB path LF */ strbuf_reset(&line); strbuf_addf(&line, "%06o %s %s\t", - mode, type, sha1_to_hex(sha1)); + mode & ~NO_DELTA, type, sha1_to_hex(sha1)); quote_c_style(path, &line, NULL, 0); strbuf_addch(&line, '\n'); } @@ -3338,10 +3379,10 @@ int main(int argc, const char **argv) fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count); fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count); - fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); - fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); - fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); - fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); + fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); fprintf(stderr, " atoms: %10u\n", atom_cnt); @@ -224,13 +224,15 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) static int fsck_ident(char **ident, struct object *obj, fsck_error error_func) { - if (**ident == '<' || **ident == '\n') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); - *ident += strcspn(*ident, "<\n"); - if ((*ident)[-1] != ' ') + if (**ident == '<') return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); + *ident += strcspn(*ident, "<>\n"); + if (**ident == '>') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name"); if (**ident != '<') return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email"); + if ((*ident)[-1] != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); (*ident)++; *ident += strcspn(*ident, "<>\n"); if (**ident != '>') @@ -295,7 +295,7 @@ split_patches () { perl -ne 'BEGIN { $subject = 0 } if ($subject > 1) { print ; } elsif (/^\s+$/) { next ; } - elsif (/^Author:/) { print s/Author/From/ ; } + elsif (/^Author:/) { s/Author/From/ ; print ;} elsif (/^(From|Date)/) { print ; } elsif ($subject) { $subject = 2 ; @@ -312,7 +312,8 @@ split_patches () { msgnum= ;; *) - if test -n "$parse_patch" ; then + if test -n "$patch_format" + then clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")" else clean_abort "$(gettext "Patch format detection failed.")" diff --git a/git-bisect.sh b/git-bisect.sh index e0ca3fb853..2524060475 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -45,10 +45,7 @@ bisect_head() bisect_autostart() { test -s "$GIT_DIR/BISECT_START" || { - ( - gettext "You need to start by \"git bisect start\"" && - echo - ) >&2 + gettextln "You need to start by \"git bisect start\"" >&2 if test -t 0 then # TRANSLATORS: Make sure to include [Y] and [n] in your @@ -272,10 +269,7 @@ bisect_next_check() { t,,good) # have bad but not good. we could bisect although # this is less optimum. - ( - gettext "Warning: bisecting only with a bad commit." && - echo - ) >&2 + gettextln "Warning: bisecting only with a bad commit." >&2 if test -t 0 then # TRANSLATORS: Make sure to include [Y] and [n] in your @@ -291,18 +285,12 @@ bisect_next_check() { if test -s "$GIT_DIR/BISECT_START" then - ( - gettext "You need to give me at least one good and one bad revisions. -(You can use \"git bisect bad\" and \"git bisect good\" for that.)" && - echo - ) >&2 + gettextln "You need to give me at least one good and one bad revisions. +(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2 else - ( - gettext "You need to start by \"git bisect start\". + gettextln "You need to start by \"git bisect start\". You then need to give me at least one good and one bad revisions. -(You can use \"git bisect bad\" and \"git bisect good\" for that.)" && - echo - ) >&2 +(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2 fi exit 1 ;; esac @@ -355,7 +343,7 @@ bisect_visualize() { bisect_reset() { test -s "$GIT_DIR/BISECT_START" || { - gettext "We are not bisecting."; echo + gettextln "We are not bisecting." return } case "$#" in @@ -428,18 +416,15 @@ bisect_run () { while true do command="$@" - eval_gettext "running \$command"; echo + eval_gettextln "running \$command" "$@" res=$? # Check for really bad run error. if [ $res -lt 0 -o $res -ge 128 ] then - ( - eval_gettext "bisect run failed: -exit code \$res from '\$command' is < 0 or >= 128" && - echo - ) >&2 + eval_gettextln "bisect run failed: +exit code \$res from '\$command' is < 0 or >= 128" >&2 exit $res fi @@ -464,26 +449,20 @@ exit code \$res from '\$command' is < 0 or >= 128" && if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ > /dev/null then - ( - gettext "bisect run cannot continue any more" && - echo - ) >&2 + gettextln "bisect run cannot continue any more" >&2 exit $res fi if [ $res -ne 0 ] then - ( - eval_gettext "bisect run failed: -'bisect_state \$state' exited with error code \$res" && - echo - ) >&2 + eval_gettextln "bisect run failed: +'bisect_state \$state' exited with error code \$res" >&2 exit $res fi if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null then - gettext "bisect run success"; echo + gettextln "bisect run success" exit 0; fi diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 8fc65d0400..ed630b208a 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -21,7 +21,7 @@ check_unchanged () { do echo "$MERGED seems unchanged." printf "Was the merge successful? [y/n] " - read answer + read answer || return 1 case "$answer" in y*|Y*) status=0; break ;; n*|N*) status=1; break ;; diff --git a/git-mergetool.sh b/git-mergetool.sh index 3c157bcd26..b6d463f0d0 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -72,7 +72,7 @@ describe_file () { resolve_symlink_merge () { while true; do printf "Use (l)ocal or (r)emote, or (a)bort? " - read ans + read ans || return 1 case "$ans" in [lL]*) git checkout-index -f --stage=2 -- "$MERGED" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c6ba7c1551..94f36c254c 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -472,18 +472,24 @@ do_next () { git rev-parse --verify HEAD > "$state_dir"/stopped-sha ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution status=$? + # Run in subshell because require_clean_work_tree can die. + dirty=f + (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t if test "$status" -ne 0 then warn "Execution failed: $rest" + test "$dirty" = f || + warn "and made changes to the index and/or the working tree" + warn "You can fix the problem, and then run" warn warn " git rebase --continue" warn exit "$status" - fi - # Run in subshell because require_clean_work_tree can die. - if ! (require_clean_work_tree "rebase") + elif test "$dirty" = t then + warn "Execution succeeded: $rest" + warn "but left changes to the index and/or the working tree" warn "Commit or stash your changes, and then run" warn warn " git rebase --continue" @@ -647,8 +653,24 @@ continue) then : Nothing to commit -- skip this else + if ! test -f "$author_script" + then + die "You have staged changes in your working tree. If these changes are meant to be +squashed into the previous commit, run: + + git commit --amend + +If they are meant to go into a new commit, run: + + git commit + +In both case, once you're done, continue with: + + git rebase --continue +" + fi . "$author_script" || - die "Cannot find the author identity" + die "Error trying to find the author identity to amend commit" current_head= if test -f "$amend" then diff --git a/git-remote-testgit.py b/git-remote-testgit.py index e9c832bfd3..3dc4851cfc 100644 --- a/git-remote-testgit.py +++ b/git-remote-testgit.py @@ -1,5 +1,18 @@ #!/usr/bin/env python +# This command is a simple remote-helper, that is used both as a +# testcase for the remote-helper functionality, and as an example to +# show remote-helper authors one possible implementation. +# +# This is a Git <-> Git importer/exporter, that simply uses git +# fast-import and git fast-export to consume and produce fast-import +# streams. +# +# To understand better the way things work, one can activate debug +# traces by setting (to any value) the environment variables +# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages +# from the transport-helper side, or from this example remote-helper. + # hashlib is only available in python >= 2.5 try: import hashlib diff --git a/git-svn.perl b/git-svn.perl index 89f83fd27a..351e743a90 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -89,6 +89,7 @@ my ($_stdin, $_help, $_edit, $_prefix, $_no_checkout, $_url, $_verbose, $_git_format, $_commit_url, $_tag, $_merge_info); $Git::SVN::_follow_parent = 1; +$SVN::Git::Fetcher::_placeholder_filename = ".gitignore"; $_q ||= 0; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -139,6 +140,10 @@ my %cmd = ( %fc_opts } ], clone => [ \&cmd_clone, "Initialize and fetch revisions", { 'revision|r=s' => \$_revision, + 'preserve-empty-dirs' => + \$SVN::Git::Fetcher::_preserve_empty_dirs, + 'placeholder-filename=s' => + \$SVN::Git::Fetcher::_placeholder_filename, %fc_opts, %init_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", @@ -386,6 +391,12 @@ sub do_git_init_db { my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex; command_noisy('config', "$pfx.ignore-paths", $$ignore_regex) if defined $$ignore_regex; + + if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) { + my $fname = \$SVN::Git::Fetcher::_placeholder_filename; + command_noisy('config', "$pfx.preserve-empty-dirs", 'true'); + command_noisy('config', "$pfx.placeholder-filename", $$fname); + } } sub init_subdir { @@ -497,6 +508,195 @@ sub cmd_set_tree { unlink $gs->{index}; } +sub split_merge_info_range { + my ($range) = @_; + if ($range =~ /(\d+)-(\d+)/) { + return (int($1), int($2)); + } else { + return (int($range), int($range)); + } +} + +sub combine_ranges { + my ($in) = @_; + + my @fnums = (); + my @arr = split(/,/, $in); + for my $element (@arr) { + my ($start, $end) = split_merge_info_range($element); + push @fnums, $start; + } + + my @sorted = @arr [ sort { + $fnums[$a] <=> $fnums[$b] + } 0..$#arr ]; + + my @return = (); + my $last = -1; + my $first = -1; + for my $element (@sorted) { + my ($start, $end) = split_merge_info_range($element); + + if ($last == -1) { + $first = $start; + $last = $end; + next; + } + if ($start <= $last+1) { + if ($end > $last) { + $last = $end; + } + next; + } + if ($first == $last) { + push @return, "$first"; + } else { + push @return, "$first-$last"; + } + $first = $start; + $last = $end; + } + + if ($first != -1) { + if ($first == $last) { + push @return, "$first"; + } else { + push @return, "$first-$last"; + } + } + + return join(',', @return); +} + +sub merge_revs_into_hash { + my ($hash, $minfo) = @_; + my @lines = split(' ', $minfo); + + for my $line (@lines) { + my ($branchpath, $revs) = split(/:/, $line); + + if (exists($hash->{$branchpath})) { + # Merge the two revision sets + my $combined = "$hash->{$branchpath},$revs"; + $hash->{$branchpath} = combine_ranges($combined); + } else { + # Just do range combining for consolidation + $hash->{$branchpath} = combine_ranges($revs); + } + } +} + +sub merge_merge_info { + my ($mergeinfo_one, $mergeinfo_two) = @_; + my %result_hash = (); + + merge_revs_into_hash(\%result_hash, $mergeinfo_one); + merge_revs_into_hash(\%result_hash, $mergeinfo_two); + + my $result = ''; + # Sort below is for consistency's sake + for my $branchname (sort keys(%result_hash)) { + my $revlist = $result_hash{$branchname}; + $result .= "$branchname:$revlist\n" + } + return $result; +} + +sub populate_merge_info { + my ($d, $gs, $uuid, $linear_refs, $rewritten_parent) = @_; + + my %parentshash; + read_commit_parents(\%parentshash, $d); + my @parents = @{$parentshash{$d}}; + if ($#parents > 0) { + # Merge commit + my $all_parents_ok = 1; + my $aggregate_mergeinfo = ''; + my $rooturl = $gs->repos_root; + + if (defined($rewritten_parent)) { + # Replace first parent with newly-rewritten version + shift @parents; + unshift @parents, $rewritten_parent; + } + + foreach my $parent (@parents) { + my ($branchurl, $svnrev, $paruuid) = + cmt_metadata($parent); + + unless (defined($svnrev)) { + # Should have been caught be preflight check + fatal "merge commit $d has ancestor $parent, but that change " + ."does not have git-svn metadata!"; + } + unless ($branchurl =~ /^$rooturl(.*)/) { + fatal "commit $parent git-svn metadata changed mid-run!"; + } + my $branchpath = $1; + + my $ra = Git::SVN::Ra->new($branchurl); + my (undef, undef, $props) = + $ra->get_dir(canonicalize_path("."), $svnrev); + my $par_mergeinfo = $props->{'svn:mergeinfo'}; + unless (defined $par_mergeinfo) { + $par_mergeinfo = ''; + } + # Merge previous mergeinfo values + $aggregate_mergeinfo = + merge_merge_info($aggregate_mergeinfo, + $par_mergeinfo, 0); + + next if $parent eq $parents[0]; # Skip first parent + # Add new changes being placed in tree by merge + my @cmd = (qw/rev-list --reverse/, + $parent, qw/--not/); + foreach my $par (@parents) { + unless ($par eq $parent) { + push @cmd, $par; + } + } + my @revsin = (); + my ($revlist, $ctx) = command_output_pipe(@cmd); + while (<$revlist>) { + my $irev = $_; + chomp $irev; + my (undef, $csvnrev, undef) = + cmt_metadata($irev); + unless (defined $csvnrev) { + # A child is missing SVN annotations... + # this might be OK, or might not be. + warn "W:child $irev is merged into revision " + ."$d but does not have git-svn metadata. " + ."This means git-svn cannot determine the " + ."svn revision numbers to place into the " + ."svn:mergeinfo property. You must ensure " + ."a branch is entirely committed to " + ."SVN before merging it in order for " + ."svn:mergeinfo population to function " + ."properly"; + } + push @revsin, $csvnrev; + } + command_close_pipe($revlist, $ctx); + + last unless $all_parents_ok; + + # We now have a list of all SVN revnos which are + # merged by this particular parent. Integrate them. + next if $#revsin == -1; + my $newmergeinfo = "$branchpath:" . join(',', @revsin); + $aggregate_mergeinfo = + merge_merge_info($aggregate_mergeinfo, + $newmergeinfo, 1); + } + if ($all_parents_ok and $aggregate_mergeinfo) { + return $aggregate_mergeinfo; + } + } + + return undef; +} + sub cmd_dcommit { my $head = shift; command_noisy(qw/update-index --refresh/); @@ -547,7 +747,66 @@ sub cmd_dcommit { "without --no-rebase may be required." } my $expect_url = $url; + + my $push_merge_info = eval { + command_oneline(qw/config --get svn.pushmergeinfo/) + }; + if (not defined($push_merge_info) + or $push_merge_info eq "false" + or $push_merge_info eq "no" + or $push_merge_info eq "never") { + $push_merge_info = 0; + } + + unless (defined($_merge_info) || ! $push_merge_info) { + # Preflight check of changes to ensure no issues with mergeinfo + # This includes check for uncommitted-to-SVN parents + # (other than the first parent, which we will handle), + # information from different SVN repos, and paths + # which are not underneath this repository root. + my $rooturl = $gs->repos_root; + foreach my $d (@$linear_refs) { + my %parentshash; + read_commit_parents(\%parentshash, $d); + my @realparents = @{$parentshash{$d}}; + if ($#realparents > 0) { + # Merge commit + shift @realparents; # Remove/ignore first parent + foreach my $parent (@realparents) { + my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent); + unless (defined $paruuid) { + # A parent is missing SVN annotations... + # abort the whole operation. + fatal "$parent is merged into revision $d, " + ."but does not have git-svn metadata. " + ."Either dcommit the branch or use a " + ."local cherry-pick, FF merge, or rebase " + ."instead of an explicit merge commit."; + } + + unless ($paruuid eq $uuid) { + # Parent has SVN metadata from different repository + fatal "merge parent $parent for change $d has " + ."git-svn uuid $paruuid, while current change " + ."has uuid $uuid!"; + } + + unless ($branchurl =~ /^$rooturl(.*)/) { + # This branch is very strange indeed. + fatal "merge parent $parent for $d is on branch " + ."$branchurl, which is not under the " + ."git-svn root $rooturl!"; + } + } + } + } + } + + my $rewritten_parent; Git::SVN::remove_username($expect_url); + if (defined($_merge_info)) { + $_merge_info =~ tr{ }{\n}; + } while (1) { my $d = shift @$linear_refs or last; unless (defined $last_rev) { @@ -561,6 +820,14 @@ sub cmd_dcommit { print "diff-tree $d~1 $d\n"; } else { my $cmt_rev; + + unless (defined($_merge_info) || ! $push_merge_info) { + $_merge_info = populate_merge_info($d, $gs, + $uuid, + $linear_refs, + $rewritten_parent); + } + my %ed_opts = ( r => $last_rev, log => get_commit_entry($d)->{log}, ra => Git::SVN::Ra->new($url), @@ -603,6 +870,9 @@ sub cmd_dcommit { @finish = qw/reset --mixed/; } command_noisy(@finish, $gs->refname); + + $rewritten_parent = command_oneline(qw/rev-parse HEAD/); + if (@diff) { @refs = (); my ($url_, $rev_, $uuid_, $gs_) = @@ -3011,7 +3281,7 @@ sub other_gs { 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); + last if ($url eq $gs->metadata_url); $ref_id .= '-'; } print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1; @@ -4080,12 +4350,13 @@ sub _read_password { } package SVN::Git::Fetcher; -use vars qw/@ISA/; +use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename + @deleted_gpath %added_placeholder $repo_id/; use strict; use warnings; use Carp qw/croak/; +use File::Basename qw/dirname/; use IO::File qw//; -use vars qw/$_ignore_regex/; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -4097,8 +4368,34 @@ sub new { $self->{empty_symlinks} = _mark_empty_symlinks($git_svn, $switch_path); } - $self->{ignore_regex} = eval { command_oneline('config', '--get', - "svn-remote.$git_svn->{repo_id}.ignore-paths") }; + + # some options are read globally, but can be overridden locally + # per [svn-remote "..."] section. Command-line options will *NOT* + # override options set in an [svn-remote "..."] section + $repo_id = $git_svn->{repo_id}; + my $k = "svn-remote.$repo_id.ignore-paths"; + my $v = eval { command_oneline('config', '--get', $k) }; + $self->{ignore_regex} = $v; + + $k = "svn-remote.$repo_id.preserve-empty-dirs"; + $v = eval { command_oneline('config', '--get', '--bool', $k) }; + if ($v && $v eq 'true') { + $_preserve_empty_dirs = 1; + $k = "svn-remote.$repo_id.placeholder-filename"; + $v = eval { command_oneline('config', '--get', $k) }; + $_placeholder_filename = $v; + } + + # Load the list of placeholder files added during previous invocations. + $k = "svn-remote.$repo_id.added-placeholder"; + $v = eval { command_oneline('config', '--get-all', $k) }; + if ($_preserve_empty_dirs && $v) { + # command() prints errors to stderr, so we only call it if + # command_oneline() succeeded. + my @v = command('config', '--get-all', $k); + $added_placeholder{ dirname($_) } = $_ foreach @v; + } + $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -4227,6 +4524,8 @@ sub delete_entry { $self->{gii}->remove($gpath); print "\tD\t$gpath\n" unless $::_q; } + # Don't add to @deleted_gpath if we're deleting a placeholder file. + push @deleted_gpath, $gpath unless $added_placeholder{dirname($path)}; $self->{empty}->{$path} = 0; undef; } @@ -4259,7 +4558,15 @@ sub add_file { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $mode = '100644'; + + if ($added_placeholder{$dir}) { + # Remove our placeholder file, if we created one. + delete_entry($self, $added_placeholder{$dir}) + unless $path eq $added_placeholder{$dir}; + delete $added_placeholder{$dir} + } } + { path => $path, mode_a => $mode, mode_b => $mode, pool => SVN::Pool->new, action => 'A' }; } @@ -4277,6 +4584,7 @@ sub add_directory { chomp; $self->{gii}->remove($_); print "\tD\t$_\n" unless $::_q; + push @deleted_gpath, $gpath; } command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0; @@ -4284,6 +4592,13 @@ sub add_directory { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $self->{empty}->{$path} = 1; + + if ($added_placeholder{$dir}) { + # Remove our placeholder file, if we created one. + delete_entry($self, $added_placeholder{$dir}); + delete $added_placeholder{$dir} + } + out: { path => $path }; } @@ -4447,12 +4762,96 @@ sub abort_edit { sub close_edit { my $self = shift; + + if ($_preserve_empty_dirs) { + my @empty_dirs; + + # Any entry flagged as empty that also has an associated + # dir_prop represents a newly created empty directory. + foreach my $i (keys %{$self->{empty}}) { + push @empty_dirs, $i if exists $self->{dir_prop}->{$i}; + } + + # Search for directories that have become empty due subsequent + # file deletes. + push @empty_dirs, $self->find_empty_directories(); + + # Finally, add a placeholder file to each empty directory. + $self->add_placeholder_file($_) foreach (@empty_dirs); + + $self->stash_placeholder_list(); + } + $self->{git_commit_ok} = 1; $self->{nr} = $self->{gii}->{nr}; delete $self->{gii}; $self->SUPER::close_edit(@_); } +sub find_empty_directories { + my ($self) = @_; + my @empty_dirs; + my %dirs = map { dirname($_) => 1 } @deleted_gpath; + + foreach my $dir (sort keys %dirs) { + next if $dir eq "."; + + # If there have been any additions to this directory, there is + # no reason to check if it is empty. + my $skip_added = 0; + foreach my $t (qw/dir_prop file_prop/) { + foreach my $path (keys %{ $self->{$t} }) { + if (exists $self->{$t}->{dirname($path)}) { + $skip_added = 1; + last; + } + } + last if $skip_added; + } + next if $skip_added; + + # Use `git ls-tree` to get the filenames of this directory + # that existed prior to this particular commit. + my $ls = command('ls-tree', '-z', '--name-only', + $self->{c}, "$dir/"); + my %files = map { $_ => 1 } split(/\0/, $ls); + + # Remove the filenames that were deleted during this commit. + delete $files{$_} foreach (@deleted_gpath); + + # Report the directory if there are no filenames left. + push @empty_dirs, $dir unless (scalar %files); + } + @empty_dirs; +} + +sub add_placeholder_file { + my ($self, $dir) = @_; + my $path = "$dir/$_placeholder_filename"; + my $gpath = $self->git_path($path); + + my $fh = $::_repository->temp_acquire($gpath); + my $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh)); + Git::temp_release($fh, 1); + $self->{gii}->update('100644', $hash, $gpath) or croak $!; + + # The directory should no longer be considered empty. + delete $self->{empty}->{$dir} if exists $self->{empty}->{$dir}; + + # Keep track of any placeholder files we create. + $added_placeholder{$dir} = $path; +} + +sub stash_placeholder_list { + my ($self) = @_; + my $k = "svn-remote.$repo_id.added-placeholder"; + my $v = eval { command_oneline('config', '--get-all', $k) }; + command_noisy('config', '--unset-all', $k) if $v; + foreach (values %added_placeholder) { + command_noisy('config', '--add', $k, $_); + } +} + package SVN::Git::Editor; use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/; use strict; @@ -473,6 +473,8 @@ static void execv_dashed_external(const char **argv) const char *tmp; int status; + if (use_pager == -1) + use_pager = check_pager_config(argv[0]); commit_pager_choice(); strbuf_addf(&cmd, "git-%s", argv[0]); @@ -347,7 +347,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)) + if (!want_color(graph->revs->diffopt.use_color)) return column_colors_max; return graph->default_column_color; } @@ -137,16 +137,50 @@ static void free_pcre_regexp(struct grep_pat *p) } #endif /* !USE_LIBPCRE */ +static int is_fixed(const char *s, size_t len) +{ + size_t i; + + /* regcomp cannot accept patterns with NULs so we + * consider any pattern containing a NUL fixed. + */ + if (memchr(s, 0, len)) + return 1; + + for (i = 0; i < len; i++) { + if (is_regex_special(s[i])) + return 0; + } + + return 1; +} + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; - p->fixed = opt->fixed; - if (p->fixed) + if (opt->fixed || is_fixed(p->pattern, p->patternlen)) + p->fixed = 1; + else + p->fixed = 0; + + if (p->fixed) { + if (opt->regflags & REG_ICASE || p->ignore_case) { + static char trans[256]; + int i; + for (i = 0; i < 256; i++) + trans[i] = tolower(i); + p->kws = kwsalloc(trans); + } else { + p->kws = kwsalloc(NULL); + } + kwsincr(p->kws, p->pattern, p->patternlen); + kwsprep(p->kws); return; + } if (opt->pcre) { compile_pcre_regexp(p, opt); @@ -395,7 +429,9 @@ void free_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - if (p->pcre_regexp) + if (p->kws) + kwsfree(p->kws); + else if (p->pcre_regexp) free_pcre_regexp(p); else regfree(&p->regexp); @@ -430,7 +466,7 @@ static int word_char(char ch) static void output_color(struct grep_opt *opt, const void *data, size_t size, const char *color) { - if (opt->color && color && color[0]) { + if (want_color(opt->color) && color && color[0]) { opt->output(opt, color, strlen(color)); opt->output(opt, data, size); opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET)); @@ -455,26 +491,14 @@ static void show_name(struct grep_opt *opt, const char *name) static int fixmatch(struct grep_pat *p, char *line, char *eol, regmatch_t *match) { - char *hit; - - if (p->ignore_case) { - char *s = line; - do { - hit = strcasestr(s, p->pattern); - if (hit) - break; - s += strlen(s) + 1; - } while (s < eol); - } else - hit = memmem(line, eol - line, p->pattern, p->patternlen); - - if (!hit) { + struct kwsmatch kwsm; + size_t offset = kwsexec(p->kws, line, eol - line, &kwsm); + if (offset == -1) { match->rm_so = match->rm_eo = -1; return REG_NOMATCH; - } - else { - match->rm_so = hit - line; - match->rm_eo = match->rm_so + p->patternlen; + } else { + match->rm_so = offset; + match->rm_eo = match->rm_so + kwsm.size[0]; return 0; } } @@ -7,6 +7,7 @@ typedef int pcre; typedef int pcre_extra; #endif +#include "kwset.h" enum grep_pat_token { GREP_PATTERN, @@ -41,6 +42,7 @@ struct grep_pat { regex_t regexp; pcre *pcre_regexp; pcre_extra *pcre_extra_info; + kwset_t kws; unsigned fixed:1; unsigned ignore_case:1; unsigned word_regexp:1; diff --git a/http-fetch.c b/http-fetch.c index 3af4c71bd0..8c4c5d2224 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -56,6 +56,10 @@ int main(int argc, const char **argv) commits = 1; } + if (get_all == 0) + warning("http-fetch: use without -a is deprecated.\n" + "In a future release, -a will become the default."); + if (argv[arg]) str_end_url_with_slash(argv[arg], &url); diff --git a/kwset.c b/kwset.c new file mode 100644 index 0000000000..956ae72950 --- /dev/null +++ b/kwset.c @@ -0,0 +1,771 @@ +/* + * This file has been copied from commit e7ac713d^ in the GNU grep git + * repository. A few small changes have been made to adapt the code to + * Git. + */ + +/* kwset.c - search for any of a set of keywords. + Copyright 1989, 1998, 2000, 2005 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA + 02110-1301, USA. */ + +/* Written August 1989 by Mike Haertel. + The author may be reached (Email) at the address mike@ai.mit.edu, + or (US mail) as Mike Haertel c/o Free Software Foundation. */ + +/* The algorithm implemented by these routines bears a startling resemblence + to one discovered by Beate Commentz-Walter, although it is not identical. + See "A String Matching Algorithm Fast on the Average," Technical Report, + IBM-Germany, Scientific Center Heidelberg, Tiergartenstrasse 15, D-6900 + Heidelberg, Germany. See also Aho, A.V., and M. Corasick, "Efficient + String Matching: An Aid to Bibliographic Search," CACM June 1975, + Vol. 18, No. 6, which describes the failure function used below. */ + +#include "cache.h" + +#include "kwset.h" +#include "compat/obstack.h" + +#define NCHAR (UCHAR_MAX + 1) +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + +#define U(c) ((unsigned char) (c)) + +/* Balanced tree of edges and labels leaving a given trie node. */ +struct tree +{ + struct tree *llink; /* Left link; MUST be first field. */ + struct tree *rlink; /* Right link (to larger labels). */ + struct trie *trie; /* Trie node pointed to by this edge. */ + unsigned char label; /* Label on this edge. */ + char balance; /* Difference in depths of subtrees. */ +}; + +/* Node of a trie representing a set of reversed keywords. */ +struct trie +{ + unsigned int accepting; /* Word index of accepted word, or zero. */ + struct tree *links; /* Tree of edges leaving this node. */ + struct trie *parent; /* Parent of this node. */ + struct trie *next; /* List of all trie nodes in level order. */ + struct trie *fail; /* Aho-Corasick failure function. */ + int depth; /* Depth of this node from the root. */ + int shift; /* Shift function for search failures. */ + int maxshift; /* Max shift of self and descendents. */ +}; + +/* Structure returned opaquely to the caller, containing everything. */ +struct kwset +{ + struct obstack obstack; /* Obstack for node allocation. */ + int words; /* Number of words in the trie. */ + struct trie *trie; /* The trie itself. */ + int mind; /* Minimum depth of an accepting node. */ + int maxd; /* Maximum depth of any node. */ + unsigned char delta[NCHAR]; /* Delta table for rapid search. */ + struct trie *next[NCHAR]; /* Table of children of the root. */ + char *target; /* Target string if there's only one. */ + int mind2; /* Used in Boyer-Moore search for one string. */ + char const *trans; /* Character translation table. */ +}; + +/* Allocate and initialize a keyword set object, returning an opaque + pointer to it. Return NULL if memory is not available. */ +kwset_t +kwsalloc (char const *trans) +{ + struct kwset *kwset; + + kwset = (struct kwset *) xmalloc(sizeof (struct kwset)); + + obstack_init(&kwset->obstack); + kwset->words = 0; + kwset->trie + = (struct trie *) obstack_alloc(&kwset->obstack, sizeof (struct trie)); + if (!kwset->trie) + { + kwsfree((kwset_t) kwset); + return NULL; + } + kwset->trie->accepting = 0; + kwset->trie->links = NULL; + kwset->trie->parent = NULL; + kwset->trie->next = NULL; + kwset->trie->fail = NULL; + kwset->trie->depth = 0; + kwset->trie->shift = 0; + kwset->mind = INT_MAX; + kwset->maxd = -1; + kwset->target = NULL; + kwset->trans = trans; + + return (kwset_t) kwset; +} + +/* This upper bound is valid for CHAR_BIT >= 4 and + exact for CHAR_BIT in { 4..11, 13, 15, 17, 19 }. */ +#define DEPTH_SIZE (CHAR_BIT + CHAR_BIT/2) + +/* Add the given string to the contents of the keyword set. Return NULL + for success, an error message otherwise. */ +const char * +kwsincr (kwset_t kws, char const *text, size_t len) +{ + struct kwset *kwset; + register struct trie *trie; + register unsigned char label; + register struct tree *link; + register int depth; + struct tree *links[DEPTH_SIZE]; + enum { L, R } dirs[DEPTH_SIZE]; + struct tree *t, *r, *l, *rl, *lr; + + kwset = (struct kwset *) kws; + trie = kwset->trie; + text += len; + + /* Descend the trie (built of reversed keywords) character-by-character, + installing new nodes when necessary. */ + while (len--) + { + label = kwset->trans ? kwset->trans[U(*--text)] : *--text; + + /* Descend the tree of outgoing links for this trie node, + looking for the current character and keeping track + of the path followed. */ + link = trie->links; + links[0] = (struct tree *) &trie->links; + dirs[0] = L; + depth = 1; + + while (link && label != link->label) + { + links[depth] = link; + if (label < link->label) + dirs[depth++] = L, link = link->llink; + else + dirs[depth++] = R, link = link->rlink; + } + + /* The current character doesn't have an outgoing link at + this trie node, so build a new trie node and install + a link in the current trie node's tree. */ + if (!link) + { + link = (struct tree *) obstack_alloc(&kwset->obstack, + sizeof (struct tree)); + if (!link) + return "memory exhausted"; + link->llink = NULL; + link->rlink = NULL; + link->trie = (struct trie *) obstack_alloc(&kwset->obstack, + sizeof (struct trie)); + if (!link->trie) + { + obstack_free(&kwset->obstack, link); + return "memory exhausted"; + } + link->trie->accepting = 0; + link->trie->links = NULL; + link->trie->parent = trie; + link->trie->next = NULL; + link->trie->fail = NULL; + link->trie->depth = trie->depth + 1; + link->trie->shift = 0; + link->label = label; + link->balance = 0; + + /* Install the new tree node in its parent. */ + if (dirs[--depth] == L) + links[depth]->llink = link; + else + links[depth]->rlink = link; + + /* Back up the tree fixing the balance flags. */ + while (depth && !links[depth]->balance) + { + if (dirs[depth] == L) + --links[depth]->balance; + else + ++links[depth]->balance; + --depth; + } + + /* Rebalance the tree by pointer rotations if necessary. */ + if (depth && ((dirs[depth] == L && --links[depth]->balance) + || (dirs[depth] == R && ++links[depth]->balance))) + { + switch (links[depth]->balance) + { + case (char) -2: + switch (dirs[depth + 1]) + { + case L: + r = links[depth], t = r->llink, rl = t->rlink; + t->rlink = r, r->llink = rl; + t->balance = r->balance = 0; + break; + case R: + r = links[depth], l = r->llink, t = l->rlink; + rl = t->rlink, lr = t->llink; + t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl; + l->balance = t->balance != 1 ? 0 : -1; + r->balance = t->balance != (char) -1 ? 0 : 1; + t->balance = 0; + break; + default: + abort (); + } + break; + case 2: + switch (dirs[depth + 1]) + { + case R: + l = links[depth], t = l->rlink, lr = t->llink; + t->llink = l, l->rlink = lr; + t->balance = l->balance = 0; + break; + case L: + l = links[depth], r = l->rlink, t = r->llink; + lr = t->llink, rl = t->rlink; + t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl; + l->balance = t->balance != 1 ? 0 : -1; + r->balance = t->balance != (char) -1 ? 0 : 1; + t->balance = 0; + break; + default: + abort (); + } + break; + default: + abort (); + } + + if (dirs[depth - 1] == L) + links[depth - 1]->llink = t; + else + links[depth - 1]->rlink = t; + } + } + + trie = link->trie; + } + + /* Mark the node we finally reached as accepting, encoding the + index number of this word in the keyword set so far. */ + if (!trie->accepting) + trie->accepting = 1 + 2 * kwset->words; + ++kwset->words; + + /* Keep track of the longest and shortest string of the keyword set. */ + if (trie->depth < kwset->mind) + kwset->mind = trie->depth; + if (trie->depth > kwset->maxd) + kwset->maxd = trie->depth; + + return NULL; +} + +/* Enqueue the trie nodes referenced from the given tree in the + given queue. */ +static void +enqueue (struct tree *tree, struct trie **last) +{ + if (!tree) + return; + enqueue(tree->llink, last); + enqueue(tree->rlink, last); + (*last) = (*last)->next = tree->trie; +} + +/* Compute the Aho-Corasick failure function for the trie nodes referenced + from the given tree, given the failure function for their parent as + well as a last resort failure node. */ +static void +treefails (register struct tree const *tree, struct trie const *fail, + struct trie *recourse) +{ + register struct tree *link; + + if (!tree) + return; + + treefails(tree->llink, fail, recourse); + treefails(tree->rlink, fail, recourse); + + /* Find, in the chain of fails going back to the root, the first + node that has a descendent on the current label. */ + while (fail) + { + link = fail->links; + while (link && tree->label != link->label) + if (tree->label < link->label) + link = link->llink; + else + link = link->rlink; + if (link) + { + tree->trie->fail = link->trie; + return; + } + fail = fail->fail; + } + + tree->trie->fail = recourse; +} + +/* Set delta entries for the links of the given tree such that + the preexisting delta value is larger than the current depth. */ +static void +treedelta (register struct tree const *tree, + register unsigned int depth, + unsigned char delta[]) +{ + if (!tree) + return; + treedelta(tree->llink, depth, delta); + treedelta(tree->rlink, depth, delta); + if (depth < delta[tree->label]) + delta[tree->label] = depth; +} + +/* Return true if A has every label in B. */ +static int +hasevery (register struct tree const *a, register struct tree const *b) +{ + if (!b) + return 1; + if (!hasevery(a, b->llink)) + return 0; + if (!hasevery(a, b->rlink)) + return 0; + while (a && b->label != a->label) + if (b->label < a->label) + a = a->llink; + else + a = a->rlink; + return !!a; +} + +/* Compute a vector, indexed by character code, of the trie nodes + referenced from the given tree. */ +static void +treenext (struct tree const *tree, struct trie *next[]) +{ + if (!tree) + return; + treenext(tree->llink, next); + treenext(tree->rlink, next); + next[tree->label] = tree->trie; +} + +/* Compute the shift for each trie node, as well as the delta + table and next cache for the given keyword set. */ +const char * +kwsprep (kwset_t kws) +{ + register struct kwset *kwset; + register int i; + register struct trie *curr; + register char const *trans; + unsigned char delta[NCHAR]; + + kwset = (struct kwset *) kws; + + /* Initial values for the delta table; will be changed later. The + delta entry for a given character is the smallest depth of any + node at which an outgoing edge is labeled by that character. */ + memset(delta, kwset->mind < UCHAR_MAX ? kwset->mind : UCHAR_MAX, NCHAR); + + /* Check if we can use the simple boyer-moore algorithm, instead + of the hairy commentz-walter algorithm. */ + if (kwset->words == 1 && kwset->trans == NULL) + { + char c; + + /* Looking for just one string. Extract it from the trie. */ + kwset->target = obstack_alloc(&kwset->obstack, kwset->mind); + if (!kwset->target) + return "memory exhausted"; + for (i = kwset->mind - 1, curr = kwset->trie; i >= 0; --i) + { + kwset->target[i] = curr->links->label; + curr = curr->links->trie; + } + /* Build the Boyer Moore delta. Boy that's easy compared to CW. */ + for (i = 0; i < kwset->mind; ++i) + delta[U(kwset->target[i])] = kwset->mind - (i + 1); + /* Find the minimal delta2 shift that we might make after + a backwards match has failed. */ + c = kwset->target[kwset->mind - 1]; + for (i = kwset->mind - 2; i >= 0; --i) + if (kwset->target[i] == c) + break; + kwset->mind2 = kwset->mind - (i + 1); + } + else + { + register struct trie *fail; + struct trie *last, *next[NCHAR]; + + /* Traverse the nodes of the trie in level order, simultaneously + computing the delta table, failure function, and shift function. */ + for (curr = last = kwset->trie; curr; curr = curr->next) + { + /* Enqueue the immediate descendents in the level order queue. */ + enqueue(curr->links, &last); + + curr->shift = kwset->mind; + curr->maxshift = kwset->mind; + + /* Update the delta table for the descendents of this node. */ + treedelta(curr->links, curr->depth, delta); + + /* Compute the failure function for the decendents of this node. */ + treefails(curr->links, curr->fail, kwset->trie); + + /* Update the shifts at each node in the current node's chain + of fails back to the root. */ + for (fail = curr->fail; fail; fail = fail->fail) + { + /* If the current node has some outgoing edge that the fail + doesn't, then the shift at the fail should be no larger + than the difference of their depths. */ + if (!hasevery(fail->links, curr->links)) + if (curr->depth - fail->depth < fail->shift) + fail->shift = curr->depth - fail->depth; + + /* If the current node is accepting then the shift at the + fail and its descendents should be no larger than the + difference of their depths. */ + if (curr->accepting && fail->maxshift > curr->depth - fail->depth) + fail->maxshift = curr->depth - fail->depth; + } + } + + /* Traverse the trie in level order again, fixing up all nodes whose + shift exceeds their inherited maxshift. */ + for (curr = kwset->trie->next; curr; curr = curr->next) + { + if (curr->maxshift > curr->parent->maxshift) + curr->maxshift = curr->parent->maxshift; + if (curr->shift > curr->maxshift) + curr->shift = curr->maxshift; + } + + /* Create a vector, indexed by character code, of the outgoing links + from the root node. */ + for (i = 0; i < NCHAR; ++i) + next[i] = NULL; + treenext(kwset->trie->links, next); + + if ((trans = kwset->trans) != NULL) + for (i = 0; i < NCHAR; ++i) + kwset->next[i] = next[U(trans[i])]; + else + memcpy(kwset->next, next, NCHAR * sizeof(struct trie *)); + } + + /* Fix things up for any translation table. */ + if ((trans = kwset->trans) != NULL) + for (i = 0; i < NCHAR; ++i) + kwset->delta[i] = delta[U(trans[i])]; + else + memcpy(kwset->delta, delta, NCHAR); + + return NULL; +} + +/* Fast boyer-moore search. */ +static size_t +bmexec (kwset_t kws, char const *text, size_t size) +{ + struct kwset const *kwset; + register unsigned char const *d1; + register char const *ep, *sp, *tp; + register int d, gc, i, len, md2; + + kwset = (struct kwset const *) kws; + len = kwset->mind; + + if (len == 0) + return 0; + if (len > size) + return -1; + if (len == 1) + { + tp = memchr (text, kwset->target[0], size); + return tp ? tp - text : -1; + } + + d1 = kwset->delta; + sp = kwset->target + len; + gc = U(sp[-2]); + md2 = kwset->mind2; + tp = text + len; + + /* Significance of 12: 1 (initial offset) + 10 (skip loop) + 1 (md2). */ + if (size > 12 * len) + /* 11 is not a bug, the initial offset happens only once. */ + for (ep = text + size - 11 * len;;) + { + while (tp <= ep) + { + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + if (d == 0) + goto found; + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + if (d == 0) + goto found; + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + if (d == 0) + goto found; + d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])], tp += d; + } + break; + found: + if (U(tp[-2]) == gc) + { + for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i) + ; + if (i > len) + return tp - len - text; + } + tp += md2; + } + + /* Now we have only a few characters left to search. We + carefully avoid ever producing an out-of-bounds pointer. */ + ep = text + size; + d = d1[U(tp[-1])]; + while (d <= ep - tp) + { + d = d1[U((tp += d)[-1])]; + if (d != 0) + continue; + if (U(tp[-2]) == gc) + { + for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i) + ; + if (i > len) + return tp - len - text; + } + d = md2; + } + + return -1; +} + +/* Hairy multiple string search. */ +static size_t +cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch) +{ + struct kwset const *kwset; + struct trie * const *next; + struct trie const *trie; + struct trie const *accept; + char const *beg, *lim, *mch, *lmch; + register unsigned char c; + register unsigned char const *delta; + register int d; + register char const *end, *qlim; + register struct tree const *tree; + register char const *trans; + + accept = NULL; + + /* Initialize register copies and look for easy ways out. */ + kwset = (struct kwset *) kws; + if (len < kwset->mind) + return -1; + next = kwset->next; + delta = kwset->delta; + trans = kwset->trans; + lim = text + len; + end = text; + if ((d = kwset->mind) != 0) + mch = NULL; + else + { + mch = text, accept = kwset->trie; + goto match; + } + + if (len >= 4 * kwset->mind) + qlim = lim - 4 * kwset->mind; + else + qlim = NULL; + + while (lim - end >= d) + { + if (qlim && end <= qlim) + { + end += d - 1; + while ((d = delta[c = *end]) && end < qlim) + { + end += d; + end += delta[U(*end)]; + end += delta[U(*end)]; + } + ++end; + } + else + d = delta[c = (end += d)[-1]]; + if (d) + continue; + beg = end - 1; + trie = next[c]; + if (trie->accepting) + { + mch = beg; + accept = trie; + } + d = trie->shift; + while (beg > text) + { + c = trans ? trans[U(*--beg)] : *--beg; + tree = trie->links; + while (tree && c != tree->label) + if (c < tree->label) + tree = tree->llink; + else + tree = tree->rlink; + if (tree) + { + trie = tree->trie; + if (trie->accepting) + { + mch = beg; + accept = trie; + } + } + else + break; + d = trie->shift; + } + if (mch) + goto match; + } + return -1; + + match: + /* Given a known match, find the longest possible match anchored + at or before its starting point. This is nearly a verbatim + copy of the preceding main search loops. */ + if (lim - mch > kwset->maxd) + lim = mch + kwset->maxd; + lmch = 0; + d = 1; + while (lim - end >= d) + { + if ((d = delta[c = (end += d)[-1]]) != 0) + continue; + beg = end - 1; + if (!(trie = next[c])) + { + d = 1; + continue; + } + if (trie->accepting && beg <= mch) + { + lmch = beg; + accept = trie; + } + d = trie->shift; + while (beg > text) + { + c = trans ? trans[U(*--beg)] : *--beg; + tree = trie->links; + while (tree && c != tree->label) + if (c < tree->label) + tree = tree->llink; + else + tree = tree->rlink; + if (tree) + { + trie = tree->trie; + if (trie->accepting && beg <= mch) + { + lmch = beg; + accept = trie; + } + } + else + break; + d = trie->shift; + } + if (lmch) + { + mch = lmch; + goto match; + } + if (!d) + d = 1; + } + + if (kwsmatch) + { + kwsmatch->index = accept->accepting / 2; + kwsmatch->offset[0] = mch - text; + kwsmatch->size[0] = accept->depth; + } + return mch - text; +} + +/* Search through the given text for a match of any member of the + given keyword set. Return a pointer to the first character of + the matching substring, or NULL if no match is found. If FOUNDLEN + is non-NULL store in the referenced location the length of the + matching substring. Similarly, if FOUNDIDX is non-NULL, store + in the referenced location the index number of the particular + keyword matched. */ +size_t +kwsexec (kwset_t kws, char const *text, size_t size, + struct kwsmatch *kwsmatch) +{ + struct kwset const *kwset = (struct kwset *) kws; + if (kwset->words == 1 && kwset->trans == NULL) + { + size_t ret = bmexec (kws, text, size); + if (kwsmatch != NULL && ret != (size_t) -1) + { + kwsmatch->index = 0; + kwsmatch->offset[0] = ret; + kwsmatch->size[0] = kwset->mind; + } + return ret; + } + else + return cwexec(kws, text, size, kwsmatch); +} + +/* Free the components of the given keyword set. */ +void +kwsfree (kwset_t kws) +{ + struct kwset *kwset; + + kwset = (struct kwset *) kws; + obstack_free(&kwset->obstack, NULL); + free(kws); +} diff --git a/kwset.h b/kwset.h new file mode 100644 index 0000000000..a21b2eadfd --- /dev/null +++ b/kwset.h @@ -0,0 +1,63 @@ +/* This file has been copied from commit e7ac713d^ in the GNU grep git + * repository. A few small changes have been made to adapt the code to + * Git. + */ + +/* kwset.h - header declaring the keyword set library. + Copyright (C) 1989, 1998, 2005 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA + 02110-1301, USA. */ + +/* Written August 1989 by Mike Haertel. + The author may be reached (Email) at the address mike@ai.mit.edu, + or (US mail) as Mike Haertel c/o Free Software Foundation. */ + +struct kwsmatch +{ + int index; /* Index number of matching keyword. */ + size_t offset[1]; /* Offset of each submatch. */ + size_t size[1]; /* Length of each submatch. */ +}; + +struct kwset_t; +typedef struct kwset_t* kwset_t; + +/* Return an opaque pointer to a newly allocated keyword set, or NULL + if enough memory cannot be obtained. The argument if non-NULL + specifies a table of character translations to be applied to all + pattern and search text. */ +extern kwset_t kwsalloc(char const *); + +/* Incrementally extend the keyword set to include the given string. + Return NULL for success, or an error message. Remember an index + number for each keyword included in the set. */ +extern const char *kwsincr(kwset_t, char const *, size_t); + +/* When the keyword set has been completely built, prepare it for + use. Return NULL for success, or an error message. */ +extern const char *kwsprep(kwset_t); + +/* Search through the given buffer for a member of the keyword set. + Return a pointer to the leftmost longest match found, or NULL if + no match is found. If foundlen is non-NULL, store the length of + the matching substring in the integer it points to. Similarly, + if foundindex is non-NULL, store the index of the particular + keyword found therein. */ +extern size_t kwsexec(kwset_t, char const *, size_t, struct kwsmatch *); + +/* Deallocate the given keyword set and all its associated storage. */ +extern void kwsfree(kwset_t); + diff --git a/log-tree.c b/log-tree.c index e9457019d5..24c295ea1d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -18,6 +18,7 @@ enum decoration_type { DECORATION_REF_TAG, DECORATION_REF_STASH, DECORATION_REF_HEAD, + DECORATION_GRAFTED, }; static char decoration_colors[][COLOR_MAXLEN] = { @@ -27,11 +28,12 @@ static char decoration_colors[][COLOR_MAXLEN] = { GIT_COLOR_BOLD_YELLOW, /* REF_TAG */ GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */ GIT_COLOR_BOLD_CYAN, /* REF_HEAD */ + GIT_COLOR_BOLD_BLUE, /* GRAFTED */ }; static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix) { - if (decorate_use_color) + if (want_color(decorate_use_color)) return decoration_colors[ix]; return ""; } @@ -77,7 +79,7 @@ int parse_decorate_color_config(const char *var, const int ofs, const char *valu * for showing the commit sha1, use the same check for --decorate */ #define decorate_get_color_opt(o, ix) \ - decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix) + decorate_get_color((o)->use_color, ix) static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj) { @@ -90,16 +92,32 @@ static void add_name_decoration(enum decoration_type type, const char *name, str static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { - struct object *obj = parse_object(sha1); + struct object *obj; enum decoration_type type = DECORATION_NONE; + + if (!prefixcmp(refname, "refs/replace/")) { + unsigned char original_sha1[20]; + if (!read_replace_refs) + return 0; + if (get_sha1_hex(refname + 13, original_sha1)) { + warning("invalid replace ref %s", refname); + return 0; + } + obj = parse_object(original_sha1); + if (obj) + add_name_decoration(DECORATION_GRAFTED, "replaced", obj); + return 0; + } + + obj = parse_object(sha1); if (!obj) return 0; - if (!prefixcmp(refname, "refs/heads")) + if (!prefixcmp(refname, "refs/heads/")) type = DECORATION_REF_LOCAL; - else if (!prefixcmp(refname, "refs/remotes")) + else if (!prefixcmp(refname, "refs/remotes/")) type = DECORATION_REF_REMOTE; - else if (!prefixcmp(refname, "refs/tags")) + else if (!prefixcmp(refname, "refs/tags/")) type = DECORATION_REF_TAG; else if (!prefixcmp(refname, "refs/stash")) type = DECORATION_REF_STASH; @@ -118,6 +136,15 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in return 0; } +static int add_graft_decoration(const struct commit_graft *graft, void *cb_data) +{ + struct commit *commit = lookup_commit(graft->sha1); + if (!commit) + return 0; + add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object); + return 0; +} + void load_ref_decorations(int flags) { static int loaded; @@ -125,6 +152,7 @@ void load_ref_decorations(int flags) loaded = 1; for_each_ref(add_ref_decoration, &flags); head_ref(add_ref_decoration, &flags); + for_each_commit_graft(add_graft_decoration, NULL); } } diff --git a/merge-recursive.c b/merge-recursive.c index 0804eb44e7..3efc04e04f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -66,10 +66,12 @@ static int sha_eq(const unsigned char *a, const unsigned char *b) enum rename_type { RENAME_NORMAL = 0, RENAME_DELETE, - RENAME_ONE_FILE_TO_TWO + RENAME_ONE_FILE_TO_ONE, + RENAME_ONE_FILE_TO_TWO, + RENAME_TWO_FILES_TO_ONE }; -struct rename_df_conflict_info { +struct rename_conflict_info { enum rename_type rename_type; struct diff_filepair *pair1; struct diff_filepair *pair2; @@ -77,6 +79,8 @@ struct rename_df_conflict_info { const char *branch2; struct stage_data *dst_entry1; struct stage_data *dst_entry2; + struct diff_filespec ren1_other; + struct diff_filespec ren2_other; }; /* @@ -88,34 +92,54 @@ struct stage_data { unsigned mode; unsigned char sha[20]; } stages[4]; - struct rename_df_conflict_info *rename_df_conflict_info; + struct rename_conflict_info *rename_conflict_info; unsigned processed:1; }; -static inline void setup_rename_df_conflict_info(enum rename_type rename_type, - struct diff_filepair *pair1, - struct diff_filepair *pair2, - const char *branch1, - const char *branch2, - struct stage_data *dst_entry1, - struct stage_data *dst_entry2) +static inline void setup_rename_conflict_info(enum rename_type rename_type, + struct diff_filepair *pair1, + struct diff_filepair *pair2, + const char *branch1, + const char *branch2, + struct stage_data *dst_entry1, + struct stage_data *dst_entry2, + struct merge_options *o, + struct stage_data *src_entry1, + struct stage_data *src_entry2) { - struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info)); + struct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info)); ci->rename_type = rename_type; ci->pair1 = pair1; ci->branch1 = branch1; ci->branch2 = branch2; ci->dst_entry1 = dst_entry1; - dst_entry1->rename_df_conflict_info = ci; + dst_entry1->rename_conflict_info = ci; dst_entry1->processed = 0; assert(!pair2 == !dst_entry2); if (dst_entry2) { ci->dst_entry2 = dst_entry2; ci->pair2 = pair2; - dst_entry2->rename_df_conflict_info = ci; - dst_entry2->processed = 0; + dst_entry2->rename_conflict_info = ci; + } + + if (rename_type == RENAME_TWO_FILES_TO_ONE) { + /* + * For each rename, there could have been + * modifications on the side of history where that + * file was not renamed. + */ + int ostage1 = o->branch1 == branch1 ? 3 : 2; + int ostage2 = ostage1 ^ 1; + + ci->ren1_other.path = pair1->one->path; + hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha); + ci->ren1_other.mode = src_entry1->stages[ostage1].mode; + + ci->ren2_other.path = pair2->one->path; + hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha); + ci->ren2_other.mode = src_entry2->stages[ostage2].mode; } } @@ -230,7 +254,7 @@ struct tree *write_tree_from_memory(struct merge_options *o) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) - fprintf(stderr, "BUG: %d %.*s", ce_stage(ce), + fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce), (int)ce_namelen(ce), ce->name); } die("Bug in merge-recursive.c"); @@ -333,44 +357,90 @@ static struct string_list *get_unmerged(void) return unmerged; } -static void make_room_for_directories_of_df_conflicts(struct merge_options *o, - struct string_list *entries) +static int string_list_df_name_compare(const void *a, const void *b) { - /* If there are D/F conflicts, and the paths currently exist - * in the working copy as a file, we want to remove them to - * make room for the corresponding directory. Such paths will - * later be processed in process_df_entry() at the end. If - * the corresponding directory ends up being removed by the - * merge, then the file will be reinstated at that time; - * otherwise, if the file is not supposed to be removed by the - * merge, the contents of the file will be placed in another - * unique filename. + const struct string_list_item *one = a; + const struct string_list_item *two = b; + int onelen = strlen(one->string); + int twolen = strlen(two->string); + /* + * Here we only care that entries for D/F conflicts are + * adjacent, in particular with the file of the D/F conflict + * appearing before files below the corresponding directory. + * The order of the rest of the list is irrelevant for us. * - * NOTE: This function relies on the fact that entries for a - * D/F conflict will appear adjacent in the index, with the - * entries for the file appearing before entries for paths - * below the corresponding directory. + * To achieve this, we sort with df_name_compare and provide + * the mode S_IFDIR so that D/F conflicts will sort correctly. + * We use the mode S_IFDIR for everything else for simplicity, + * since in other cases any changes in their order due to + * sorting cause no problems for us. + */ + int cmp = df_name_compare(one->string, onelen, S_IFDIR, + two->string, twolen, S_IFDIR); + /* + * Now that 'foo' and 'foo/bar' compare equal, we have to make sure + * that 'foo' comes before 'foo/bar'. */ + if (cmp) + return cmp; + return onelen - twolen; +} + +static void record_df_conflict_files(struct merge_options *o, + struct string_list *entries) +{ + /* If there is a D/F conflict and the file for such a conflict + * currently exist in the working copy, we want to allow it to be + * removed to make room for the corresponding directory if needed. + * The files underneath the directories of such D/F conflicts will + * be processed before the corresponding file involved in the D/F + * conflict. If the D/F directory ends up being removed by the + * merge, then we won't have to touch the D/F file. If the D/F + * directory needs to be written to the working copy, then the D/F + * file will simply be removed (in make_room_for_path()) to make + * room for the necessary paths. Note that if both the directory + * and the file need to be present, then the D/F file will be + * reinstated with a new unique name at the time it is processed. + */ + struct string_list df_sorted_entries; const char *last_file = NULL; int last_len = 0; int i; + /* + * If we're merging merge-bases, we don't want to bother with + * any working directory changes. + */ + if (o->call_depth) + return; + + /* Ensure D/F conflicts are adjacent in the entries list. */ + memset(&df_sorted_entries, 0, sizeof(struct string_list)); for (i = 0; i < entries->nr; i++) { - const char *path = entries->items[i].string; + struct string_list_item *next = &entries->items[i]; + string_list_append(&df_sorted_entries, next->string)->util = + next->util; + } + qsort(df_sorted_entries.items, entries->nr, sizeof(*entries->items), + string_list_df_name_compare); + + string_list_clear(&o->df_conflict_file_set, 1); + for (i = 0; i < df_sorted_entries.nr; i++) { + const char *path = df_sorted_entries.items[i].string; int len = strlen(path); - struct stage_data *e = entries->items[i].util; + struct stage_data *e = df_sorted_entries.items[i].util; /* * Check if last_file & path correspond to a D/F conflict; * i.e. whether path is last_file+'/'+<something>. - * If so, remove last_file to make room for path and friends. + * If so, record that it's okay to remove last_file to make + * room for path and friends if needed. */ if (last_file && len > last_len && memcmp(path, last_file, last_len) == 0 && path[last_len] == '/') { - output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file); - unlink(last_file); + string_list_insert(&o->df_conflict_file_set, last_file); } /* @@ -386,6 +456,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, last_file = NULL; } } + string_list_clear(&df_sorted_entries, 0); } struct rename { @@ -461,10 +532,21 @@ static struct string_list *get_renames(struct merge_options *o, return renames; } -static int update_stages_options(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - int clear, int options) +static int update_stages(const char *path, const struct diff_filespec *o, + const struct diff_filespec *a, + const struct diff_filespec *b) { + + /* + * NOTE: It is usually a bad idea to call update_stages on a path + * before calling update_file on that same path, since it can + * sometimes lead to spurious "refusing to lose untracked file..." + * messages from update_file (via make_room_for path via + * would_lose_untracked). Instead, reverse the order of the calls + * (executing update_file first and then update_stages). + */ + int clear = 1; + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK; if (clear) if (remove_file_from_cache(path)) return -1; @@ -480,23 +562,11 @@ static int update_stages_options(const char *path, struct diff_filespec *o, return 0; } -static int update_stages(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - int clear) +static void update_entry(struct stage_data *entry, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b) { - int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; - return update_stages_options(path, o, a, b, clear, options); -} - -static int update_stages_and_entry(const char *path, - struct stage_data *entry, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b, - int clear) -{ - int options; - entry->processed = 0; entry->stages[1].mode = o->mode; entry->stages[2].mode = a->mode; @@ -504,8 +574,6 @@ static int update_stages_and_entry(const char *path, hashcpy(entry->stages[1].sha, o->sha1); hashcpy(entry->stages[2].sha, a->sha1); hashcpy(entry->stages[3].sha, b->sha1); - options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK; - return update_stages_options(path, o, a, b, clear, options); } static int remove_file(struct merge_options *o, int clean, @@ -563,7 +631,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size) } } -static int would_lose_untracked(const char *path) +static int dir_in_way(const char *path, int check_working_copy) +{ + int pos, pathlen = strlen(path); + char *dirpath = xmalloc(pathlen + 2); + struct stat st; + + strcpy(dirpath, path); + dirpath[pathlen] = '/'; + dirpath[pathlen+1] = '\0'; + + pos = cache_name_pos(dirpath, pathlen+1); + + if (pos < 0) + pos = -1 - pos; + if (pos < active_nr && + !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) { + free(dirpath); + return 1; + } + + free(dirpath); + return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode); +} + +static int was_tracked(const char *path) { int pos = cache_name_pos(path, strlen(path)); @@ -580,18 +672,42 @@ static int would_lose_untracked(const char *path) switch (ce_stage(active_cache[pos])) { case 0: case 2: - return 0; + return 1; } pos++; } - return file_exists(path); + return 0; } -static int make_room_for_path(const char *path) +static int would_lose_untracked(const char *path) { - int status; + return !was_tracked(path) && file_exists(path); +} + +static int make_room_for_path(struct merge_options *o, const char *path) +{ + int status, i; const char *msg = "failed to create path '%s'%s"; + /* Unlink any D/F conflict files that are in the way */ + for (i = 0; i < o->df_conflict_file_set.nr; i++) { + const char *df_path = o->df_conflict_file_set.items[i].string; + size_t pathlen = strlen(path); + size_t df_pathlen = strlen(df_path); + if (df_pathlen < pathlen && + path[df_pathlen] == '/' && + strncmp(path, df_path, df_pathlen) == 0) { + output(o, 3, + "Removing %s to make room for subdirectory\n", + df_path); + unlink(df_path); + unsorted_string_list_delete_item(&o->df_conflict_file_set, + i, 0); + break; + } + } + + /* Make sure leading directories are created */ status = safe_create_leading_directories_const(path); if (status) { if (status == -3) { @@ -659,7 +775,7 @@ static void update_file_flags(struct merge_options *o, } } - if (make_room_for_path(path) < 0) { + if (make_room_for_path(o, path) < 0) { update_wd = 0; free(buf); goto update_index; @@ -712,9 +828,9 @@ struct merge_file_info { static int merge_3way(struct merge_options *o, mmbuffer_t *result_buf, - struct diff_filespec *one, - struct diff_filespec *a, - struct diff_filespec *b, + const struct diff_filespec *one, + const struct diff_filespec *a, + const struct diff_filespec *b, const char *branch1, const char *branch2) { @@ -771,12 +887,12 @@ static int merge_3way(struct merge_options *o, return merge_status; } -static struct merge_file_info merge_file(struct merge_options *o, - struct diff_filespec *one, - struct diff_filespec *a, - struct diff_filespec *b, - const char *branch1, - const char *branch2) +static struct merge_file_info merge_file_1(struct merge_options *o, + const struct diff_filespec *one, + const struct diff_filespec *a, + const struct diff_filespec *b, + const char *branch1, + const char *branch2) { struct merge_file_info result; result.merge = 0; @@ -845,94 +961,303 @@ static struct merge_file_info merge_file(struct merge_options *o, return result; } +static struct merge_file_info +merge_file_special_markers(struct merge_options *o, + const struct diff_filespec *one, + const struct diff_filespec *a, + const struct diff_filespec *b, + const char *branch1, + const char *filename1, + const char *branch2, + const char *filename2) +{ + char *side1 = NULL; + char *side2 = NULL; + struct merge_file_info mfi; + + if (filename1) { + side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2); + sprintf(side1, "%s:%s", branch1, filename1); + } + if (filename2) { + side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2); + sprintf(side2, "%s:%s", branch2, filename2); + } + + mfi = merge_file_1(o, one, a, b, + side1 ? side1 : branch1, side2 ? side2 : branch2); + free(side1); + free(side2); + return mfi; +} + +static struct merge_file_info merge_file(struct merge_options *o, + const char *path, + const unsigned char *o_sha, int o_mode, + const unsigned char *a_sha, int a_mode, + const unsigned char *b_sha, int b_mode, + const char *branch1, + const char *branch2) +{ + struct diff_filespec one, a, b; + + one.path = a.path = b.path = (char *)path; + hashcpy(one.sha1, o_sha); + one.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + return merge_file_1(o, &one, &a, &b, branch1, branch2); +} + +static void handle_change_delete(struct merge_options *o, + const char *path, + const unsigned char *o_sha, int o_mode, + const unsigned char *a_sha, int a_mode, + const unsigned char *b_sha, int b_mode, + const char *change, const char *change_past) +{ + char *renamed = NULL; + if (dir_in_way(path, !o->call_depth)) { + renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2); + } + + if (o->call_depth) { + /* + * We cannot arbitrarily accept either a_sha or b_sha as + * correct; since there is no true "middle point" between + * them, simply reuse the base version for virtual merge base. + */ + remove_file_from_cache(path); + update_file(o, 0, o_sha, o_mode, renamed ? renamed : path); + } else if (!a_sha) { + output(o, 1, "CONFLICT (%s/delete): %s deleted in %s " + "and %s in %s. Version %s of %s left in tree%s%s.", + change, path, o->branch1, + change_past, o->branch2, o->branch2, path, + NULL == renamed ? "" : " at ", + NULL == renamed ? "" : renamed); + update_file(o, 0, b_sha, b_mode, renamed ? renamed : path); + } else { + output(o, 1, "CONFLICT (%s/delete): %s deleted in %s " + "and %s in %s. Version %s of %s left in tree%s%s.", + change, path, o->branch2, + change_past, o->branch1, o->branch1, path, + NULL == renamed ? "" : " at ", + NULL == renamed ? "" : renamed); + if (renamed) + update_file(o, 0, a_sha, a_mode, renamed); + /* + * No need to call update_file() on path when !renamed, since + * that would needlessly touch path. We could call + * update_file_flags() with update_cache=0 and update_wd=0, + * but that's a no-op. + */ + } + free(renamed); +} + static void conflict_rename_delete(struct merge_options *o, struct diff_filepair *pair, const char *rename_branch, const char *other_branch) { - char *dest_name = pair->two->path; - int df_conflict = 0; - struct stat st; + const struct diff_filespec *orig = pair->one; + const struct diff_filespec *dest = pair->two; + const unsigned char *a_sha = NULL; + const unsigned char *b_sha = NULL; + int a_mode = 0; + int b_mode = 0; + + if (rename_branch == o->branch1) { + a_sha = dest->sha1; + a_mode = dest->mode; + } else { + b_sha = dest->sha1; + b_mode = dest->mode; + } - output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s " - "and deleted in %s", - pair->one->path, pair->two->path, rename_branch, - other_branch); - if (!o->call_depth) - update_stages(dest_name, NULL, - rename_branch == o->branch1 ? pair->two : NULL, - rename_branch == o->branch1 ? NULL : pair->two, - 1); - if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) { - dest_name = unique_path(o, dest_name, rename_branch); - df_conflict = 1; + handle_change_delete(o, + o->call_depth ? orig->path : dest->path, + orig->sha1, orig->mode, + a_sha, a_mode, + b_sha, b_mode, + "rename", "renamed"); + + if (o->call_depth) { + remove_file_from_cache(dest->path); + } else { + update_stages(dest->path, NULL, + rename_branch == o->branch1 ? dest : NULL, + rename_branch == o->branch1 ? NULL : dest); } - update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name); - if (df_conflict) - free(dest_name); + } -static void conflict_rename_rename_1to2(struct merge_options *o, - struct diff_filepair *pair1, - const char *branch1, - struct diff_filepair *pair2, - const char *branch2) +static struct diff_filespec *filespec_from_entry(struct diff_filespec *target, + struct stage_data *entry, + int stage) { - /* One file was renamed in both branches, but to different names. */ - char *del[2]; - int delp = 0; - const char *ren1_dst = pair1->two->path; - const char *ren2_dst = pair2->two->path; - const char *dst_name1 = ren1_dst; - const char *dst_name2 = ren2_dst; - struct stat st; - if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) { - dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1); - output(o, 1, "%s is a directory in %s adding as %s instead", - ren1_dst, branch2, dst_name1); + unsigned char *sha = entry->stages[stage].sha; + unsigned mode = entry->stages[stage].mode; + if (mode == 0 || is_null_sha1(sha)) + return NULL; + hashcpy(target->sha1, sha); + target->mode = mode; + return target; +} + +static void handle_file(struct merge_options *o, + struct diff_filespec *rename, + int stage, + struct rename_conflict_info *ci) +{ + char *dst_name = rename->path; + struct stage_data *dst_entry; + const char *cur_branch, *other_branch; + struct diff_filespec other; + struct diff_filespec *add; + + if (stage == 2) { + dst_entry = ci->dst_entry1; + cur_branch = ci->branch1; + other_branch = ci->branch2; + } else { + dst_entry = ci->dst_entry2; + cur_branch = ci->branch2; + other_branch = ci->branch1; } - if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) { - dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2); - output(o, 1, "%s is a directory in %s adding as %s instead", - ren2_dst, branch1, dst_name2); + + add = filespec_from_entry(&other, dst_entry, stage ^ 1); + if (add) { + char *add_name = unique_path(o, rename->path, other_branch); + update_file(o, 0, add->sha1, add->mode, add_name); + + remove_file(o, 0, rename->path, 0); + dst_name = unique_path(o, rename->path, cur_branch); + } else { + if (dir_in_way(rename->path, !o->call_depth)) { + dst_name = unique_path(o, rename->path, cur_branch); + output(o, 1, "%s is a directory in %s adding as %s instead", + rename->path, other_branch, dst_name); + } } + update_file(o, 0, rename->sha1, rename->mode, dst_name); + if (stage == 2) + update_stages(rename->path, NULL, rename, add); + else + update_stages(rename->path, NULL, add, rename); + + if (dst_name != rename->path) + free(dst_name); +} + +static void conflict_rename_rename_1to2(struct merge_options *o, + struct rename_conflict_info *ci) +{ + /* One file was renamed in both branches, but to different names. */ + struct diff_filespec *one = ci->pair1->one; + struct diff_filespec *a = ci->pair1->two; + struct diff_filespec *b = ci->pair2->two; + + output(o, 1, "CONFLICT (rename/rename): " + "Rename \"%s\"->\"%s\" in branch \"%s\" " + "rename \"%s\"->\"%s\" in \"%s\"%s", + one->path, a->path, ci->branch1, + one->path, b->path, ci->branch2, + o->call_depth ? " (left unresolved)" : ""); if (o->call_depth) { - remove_file_from_cache(dst_name1); - remove_file_from_cache(dst_name2); + struct merge_file_info mfi; + struct diff_filespec other; + struct diff_filespec *add; + mfi = merge_file(o, one->path, + one->sha1, one->mode, + a->sha1, a->mode, + b->sha1, b->mode, + ci->branch1, ci->branch2); /* - * Uncomment to leave the conflicting names in the resulting tree - * - * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1); - * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2); + * FIXME: For rename/add-source conflicts (if we could detect + * such), this is wrong. We should instead find a unique + * pathname and then either rename the add-source file to that + * unique path, or use that unique path instead of src here. */ - } else { - update_stages(ren1_dst, NULL, pair1->two, NULL, 1); - update_stages(ren2_dst, NULL, NULL, pair2->two, 1); + update_file(o, 0, mfi.sha, mfi.mode, one->path); - update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1); - update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2); + /* + * Above, we put the merged content at the merge-base's + * path. Now we usually need to delete both a->path and + * b->path. However, the rename on each side of the merge + * could also be involved in a rename/add conflict. In + * such cases, we should keep the added file around, + * resolving the conflict at that path in its favor. + */ + add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1); + if (add) + update_file(o, 0, add->sha1, add->mode, a->path); + else + remove_file_from_cache(a->path); + add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1); + if (add) + update_file(o, 0, add->sha1, add->mode, b->path); + else + remove_file_from_cache(b->path); + } else { + handle_file(o, a, 2, ci); + handle_file(o, b, 3, ci); } - while (delp--) - free(del[delp]); } static void conflict_rename_rename_2to1(struct merge_options *o, - struct rename *ren1, - const char *branch1, - struct rename *ren2, - const char *branch2) + struct rename_conflict_info *ci) { - /* Two files were renamed to the same thing. */ - char *new_path1 = unique_path(o, ren1->pair->two->path, branch1); - char *new_path2 = unique_path(o, ren2->pair->two->path, branch2); - output(o, 1, "Renaming %s to %s and %s to %s instead", - ren1->pair->one->path, new_path1, - ren2->pair->one->path, new_path2); - remove_file(o, 0, ren1->pair->two->path, 0); - update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); - update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); - free(new_path2); - free(new_path1); + /* Two files, a & b, were renamed to the same thing, c. */ + struct diff_filespec *a = ci->pair1->one; + struct diff_filespec *b = ci->pair2->one; + struct diff_filespec *c1 = ci->pair1->two; + struct diff_filespec *c2 = ci->pair2->two; + char *path = c1->path; /* == c2->path */ + struct merge_file_info mfi_c1; + struct merge_file_info mfi_c2; + + output(o, 1, "CONFLICT (rename/rename): " + "Rename %s->%s in %s. " + "Rename %s->%s in %s", + a->path, c1->path, ci->branch1, + b->path, c2->path, ci->branch2); + + remove_file(o, 1, a->path, would_lose_untracked(a->path)); + remove_file(o, 1, b->path, would_lose_untracked(b->path)); + + mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other, + o->branch1, c1->path, + o->branch2, ci->ren1_other.path); + mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2, + o->branch1, ci->ren2_other.path, + o->branch2, c2->path); + + if (o->call_depth) { + /* + * If mfi_c1.clean && mfi_c2.clean, then it might make + * sense to do a two-way merge of those results. But, I + * think in all cases, it makes sense to have the virtual + * merge base just undo the renames; they can be detected + * again later for the non-recursive merge. + */ + remove_file(o, 0, path, 0); + update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path); + update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path); + } else { + char *new_path1 = unique_path(o, path, ci->branch1); + char *new_path2 = unique_path(o, path, ci->branch2); + output(o, 1, "Renaming %s to %s and %s to %s instead", + a->path, new_path1, b->path, new_path2); + remove_file(o, 0, path, 0); + update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1); + update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2); + free(new_path2); + free(new_path1); + } } static int process_renames(struct merge_options *o, @@ -947,12 +1272,12 @@ static int process_renames(struct merge_options *o, for (i = 0; i < a_renames->nr; i++) { sre = a_renames->items[i].util; string_list_insert(&a_by_dst, sre->pair->two->path)->util - = sre->dst_entry; + = (void *)sre; } for (i = 0; i < b_renames->nr; i++) { sre = b_renames->items[i].util; string_list_insert(&b_by_dst, sre->pair->two->path)->util - = sre->dst_entry; + = (void *)sre; } for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { @@ -960,6 +1285,7 @@ static int process_renames(struct merge_options *o, struct rename *ren1 = NULL, *ren2 = NULL; const char *branch1, *branch2; const char *ren1_src, *ren1_dst; + struct string_list_item *lookup; if (i >= a_renames->nr) { ren2 = b_renames->items[j++].util; @@ -991,44 +1317,82 @@ static int process_renames(struct merge_options *o, ren1 = tmp; } - ren1->dst_entry->processed = 1; - ren1->src_entry->processed = 1; - if (ren1->processed) continue; ren1->processed = 1; + ren1->dst_entry->processed = 1; + /* BUG: We should only mark src_entry as processed if we + * are not dealing with a rename + add-source case. + */ + ren1->src_entry->processed = 1; ren1_src = ren1->pair->one->path; ren1_dst = ren1->pair->two->path; if (ren2) { + /* One file renamed on both sides */ const char *ren2_src = ren2->pair->one->path; const char *ren2_dst = ren2->pair->two->path; - /* Renamed in 1 and renamed in 2 */ + enum rename_type rename_type; if (strcmp(ren1_src, ren2_src) != 0) - die("ren1.src != ren2.src"); + die("ren1_src != ren2_src"); ren2->dst_entry->processed = 1; ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { - setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO, - ren1->pair, - ren2->pair, - branch1, - branch2, - ren1->dst_entry, - ren2->dst_entry); + rename_type = RENAME_ONE_FILE_TO_TWO; + clean_merge = 0; } else { + rename_type = RENAME_ONE_FILE_TO_ONE; + /* BUG: We should only remove ren1_src in + * the base stage (think of rename + + * add-source cases). + */ remove_file(o, 1, ren1_src, 1); - update_stages_and_entry(ren1_dst, - ren1->dst_entry, - ren1->pair->one, - ren1->pair->two, - ren2->pair->two, - 1 /* clear */); + update_entry(ren1->dst_entry, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two); } + setup_rename_conflict_info(rename_type, + ren1->pair, + ren2->pair, + branch1, + branch2, + ren1->dst_entry, + ren2->dst_entry, + o, + NULL, + NULL); + } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) { + /* Two different files renamed to the same thing */ + char *ren2_dst; + ren2 = lookup->util; + ren2_dst = ren2->pair->two->path; + if (strcmp(ren1_dst, ren2_dst) != 0) + die("ren1_dst != ren2_dst"); + + clean_merge = 0; + ren2->processed = 1; + /* + * BUG: We should only mark src_entry as processed + * if we are not dealing with a rename + add-source + * case. + */ + ren2->src_entry->processed = 1; + + setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE, + ren1->pair, + ren2->pair, + branch1, + branch2, + ren1->dst_entry, + ren2->dst_entry, + o, + ren1->src_entry, + ren2->src_entry); + } else { /* Renamed in 1, maybe changed in 2 */ - struct string_list_item *item; /* we only use sha1 and mode of these */ struct diff_filespec src_other, dst_other; int try_merge; @@ -1042,7 +1406,12 @@ static int process_renames(struct merge_options *o, int renamed_stage = a_renames == renames1 ? 2 : 3; int other_stage = a_renames == renames1 ? 3 : 2; - remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2); + /* BUG: We should only remove ren1_src in the base + * stage and in other_stage (think of rename + + * add-source case). + */ + remove_file(o, 1, ren1_src, + renamed_stage == 2 || !was_tracked(ren1_src)); hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha); src_other.mode = ren1->src_entry->stages[other_stage].mode; @@ -1051,27 +1420,33 @@ static int process_renames(struct merge_options *o, try_merge = 0; if (sha_eq(src_other.sha1, null_sha1)) { - if (string_list_has_string(&o->current_directory_set, ren1_dst)) { - ren1->dst_entry->processed = 0; - setup_rename_df_conflict_info(RENAME_DELETE, - ren1->pair, - NULL, - branch1, - branch2, - ren1->dst_entry, - NULL); - } else { - clean_merge = 0; - conflict_rename_delete(o, ren1->pair, branch1, branch2); - } + setup_rename_conflict_info(RENAME_DELETE, + ren1->pair, + NULL, + branch1, + branch2, + ren1->dst_entry, + NULL, + o, + NULL, + NULL); } else if ((dst_other.mode == ren1->pair->two->mode) && sha_eq(dst_other.sha1, ren1->pair->two->sha1)) { - /* Added file on the other side - identical to the file being - renamed: clean merge */ - update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + /* + * Added file on the other side identical to + * the file being renamed: clean merge. + * Also, there is no need to overwrite the + * file already in the working copy, so call + * update_file_flags() instead of + * update_file(). + */ + update_file_flags(o, + ren1->pair->two->sha1, + ren1->pair->two->mode, + ren1_dst, + 1, /* update_cache */ + 0 /* update_wd */); } else if (!sha_eq(dst_other.sha1, null_sha1)) { - const char *new_path; clean_merge = 0; try_merge = 1; output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. " @@ -1080,40 +1455,19 @@ static int process_renames(struct merge_options *o, ren1_dst, branch2); if (o->call_depth) { struct merge_file_info mfi; - struct diff_filespec one, a, b; - - one.path = a.path = b.path = - (char *)ren1_dst; - hashcpy(one.sha1, null_sha1); - one.mode = 0; - hashcpy(a.sha1, ren1->pair->two->sha1); - a.mode = ren1->pair->two->mode; - hashcpy(b.sha1, dst_other.sha1); - b.mode = dst_other.mode; - mfi = merge_file(o, &one, &a, &b, - branch1, - branch2); + mfi = merge_file(o, ren1_dst, null_sha1, 0, + ren1->pair->two->sha1, ren1->pair->two->mode, + dst_other.sha1, dst_other.mode, + branch1, branch2); output(o, 1, "Adding merged %s", ren1_dst); - update_file(o, 0, - mfi.sha, - mfi.mode, - ren1_dst); + update_file(o, 0, mfi.sha, mfi.mode, ren1_dst); try_merge = 0; } else { - new_path = unique_path(o, ren1_dst, branch2); + char *new_path = unique_path(o, ren1_dst, branch2); output(o, 1, "Adding as %s instead", new_path); update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); + free(new_path); } - } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) { - ren2 = item->util; - clean_merge = 0; - ren2->processed = 1; - output(o, 1, "CONFLICT (rename/rename): " - "Rename %s->%s in %s. " - "Rename %s->%s in %s", - ren1_src, ren1_dst, branch1, - ren2->pair->one->path, ren2->pair->two->path, branch2); - conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2); } else try_merge = 1; @@ -1129,16 +1483,17 @@ static int process_renames(struct merge_options *o, b = ren1->pair->two; a = &src_other; } - update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1); - if (string_list_has_string(&o->current_directory_set, ren1_dst)) { - setup_rename_df_conflict_info(RENAME_NORMAL, - ren1->pair, - NULL, - branch1, - NULL, - ren1->dst_entry, - NULL); - } + update_entry(ren1->dst_entry, one, a, b); + setup_rename_conflict_info(RENAME_NORMAL, + ren1->pair, + NULL, + branch1, + NULL, + ren1->dst_entry, + NULL, + o, + NULL, + NULL); } } } @@ -1200,29 +1555,18 @@ error_return: return ret; } -static void handle_delete_modify(struct merge_options *o, +static void handle_modify_delete(struct merge_options *o, const char *path, - const char *new_path, + unsigned char *o_sha, int o_mode, unsigned char *a_sha, int a_mode, unsigned char *b_sha, int b_mode) { - if (!a_sha) { - output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree%s%s.", - path, o->branch1, - o->branch2, o->branch2, path, - path == new_path ? "" : " at ", - path == new_path ? "" : new_path); - update_file(o, 0, b_sha, b_mode, new_path); - } else { - output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree%s%s.", - path, o->branch2, - o->branch1, o->branch1, path, - path == new_path ? "" : " at ", - path == new_path ? "" : new_path); - update_file(o, 0, a_sha, a_mode, new_path); - } + handle_change_delete(o, + path, + o_sha, o_mode, + a_sha, a_mode, + b_sha, b_mode, + "modify", "modified"); } static int merge_content(struct merge_options *o, @@ -1230,12 +1574,12 @@ static int merge_content(struct merge_options *o, unsigned char *o_sha, int o_mode, unsigned char *a_sha, int a_mode, unsigned char *b_sha, int b_mode, - const char *df_rename_conflict_branch) + struct rename_conflict_info *rename_conflict_info) { const char *reason = "content"; + const char *path1 = NULL, *path2 = NULL; struct merge_file_info mfi; struct diff_filespec one, a, b; - struct stat st; unsigned df_conflict_remains = 0; if (!o_sha) { @@ -1250,16 +1594,43 @@ static int merge_content(struct merge_options *o, hashcpy(b.sha1, b_sha); b.mode = b_mode; - mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2); - if (df_rename_conflict_branch && - lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) { - df_conflict_remains = 1; + if (rename_conflict_info) { + struct diff_filepair *pair1 = rename_conflict_info->pair1; + + path1 = (o->branch1 == rename_conflict_info->branch1) ? + pair1->two->path : pair1->one->path; + /* If rename_conflict_info->pair2 != NULL, we are in + * RENAME_ONE_FILE_TO_ONE case. Otherwise, we have a + * normal rename. + */ + path2 = (rename_conflict_info->pair2 || + o->branch2 == rename_conflict_info->branch1) ? + pair1->two->path : pair1->one->path; + + if (dir_in_way(path, !o->call_depth)) + df_conflict_remains = 1; } + mfi = merge_file_special_markers(o, &one, &a, &b, + o->branch1, path1, + o->branch2, path2); if (mfi.clean && !df_conflict_remains && - sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode) + sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) { + int path_renamed_outside_HEAD; output(o, 3, "Skipped %s (merged same as existing)", path); - else + /* + * The content merge resulted in the same file contents we + * already had. We can return early if those file contents + * are recorded at the correct path (which may not be true + * if the merge involves a rename). + */ + path_renamed_outside_HEAD = !path2 || !strcmp(path, path2); + if (!path_renamed_outside_HEAD) { + add_cacheinfo(mfi.mode, mfi.sha, path, + 0, (!o->call_depth), 0); + return mfi.clean; + } + } else output(o, 2, "Auto-merging %s", path); if (!mfi.clean) { @@ -1267,16 +1638,34 @@ static int merge_content(struct merge_options *o, reason = "submodule"; output(o, 1, "CONFLICT (%s): Merge conflict in %s", reason, path); + if (rename_conflict_info && !df_conflict_remains) + update_stages(path, &one, &a, &b); } if (df_conflict_remains) { - const char *new_path; - update_file_flags(o, mfi.sha, mfi.mode, path, - o->call_depth || mfi.clean, 0); - new_path = unique_path(o, path, df_rename_conflict_branch); - mfi.clean = 0; + char *new_path; + if (o->call_depth) { + remove_file_from_cache(path); + } else { + if (!mfi.clean) + update_stages(path, &one, &a, &b); + else { + int file_from_stage2 = was_tracked(path); + struct diff_filespec merged; + hashcpy(merged.sha1, mfi.sha); + merged.mode = mfi.mode; + + update_stages(path, NULL, + file_from_stage2 ? &merged : NULL, + file_from_stage2 ? NULL : &merged); + } + + } + new_path = unique_path(o, path, rename_conflict_info->branch1); output(o, 1, "Adding as %s instead", new_path); - update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1); + update_file(o, 0, mfi.sha, mfi.mode, new_path); + free(new_path); + mfi.clean = 0; } else { update_file(o, mfi.clean, mfi.sha, mfi.mode, path); } @@ -1301,104 +1690,15 @@ static int process_entry(struct merge_options *o, unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); - if (entry->rename_df_conflict_info) - return 1; /* Such cases are handled elsewhere. */ - - entry->processed = 1; - if (o_sha && (!a_sha || !b_sha)) { - /* Case A: Deleted in one */ - if ((!a_sha && !b_sha) || - (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) || - (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) { - /* Deleted in both or deleted in one and - * unchanged in the other */ - if (a_sha) - output(o, 2, "Removing %s", path); - /* do not touch working file if it did not exist */ - remove_file(o, 1, path, !a_sha); - } else if (string_list_has_string(&o->current_directory_set, - path)) { - entry->processed = 0; - return 1; /* Assume clean until processed */ - } else { - /* Deleted in one and changed in the other */ - clean_merge = 0; - handle_delete_modify(o, path, path, - a_sha, a_mode, b_sha, b_mode); - } - - } else if ((!o_sha && a_sha && !b_sha) || - (!o_sha && !a_sha && b_sha)) { - /* Case B: Added in one. */ - unsigned mode; - const unsigned char *sha; - - if (a_sha) { - mode = a_mode; - sha = a_sha; - } else { - mode = b_mode; - sha = b_sha; - } - if (string_list_has_string(&o->current_directory_set, path)) { - /* Handle D->F conflicts after all subfiles */ - entry->processed = 0; - return 1; /* Assume clean until processed */ - } else { - output(o, 2, "Adding %s", path); - update_file(o, 1, sha, mode, path); - } - } else if (a_sha && b_sha) { - /* Case C: Added in both (check for same permissions) and */ - /* case D: Modified in both, but differently. */ - clean_merge = merge_content(o, path, - o_sha, o_mode, a_sha, a_mode, b_sha, b_mode, - NULL); - } else if (!o_sha && !a_sha && !b_sha) { - /* - * this entry was deleted altogether. a_mode == 0 means - * we had that path and want to actively remove it. - */ - remove_file(o, 1, path, !a_mode); - } else - die("Fatal merge failure, shouldn't happen."); - - return clean_merge; -} - -/* - * Per entry merge function for D/F (and/or rename) conflicts. In the - * cases we can cleanly resolve D/F conflicts, process_entry() can - * clean out all the files below the directory for us. All D/F - * conflict cases must be handled here at the end to make sure any - * directories that can be cleaned out, are. - * - * Some rename conflicts may also be handled here that don't necessarily - * involve D/F conflicts, since the code to handle them is generic enough - * to handle those rename conflicts with or without D/F conflicts also - * being involved. - */ -static int process_df_entry(struct merge_options *o, - const char *path, struct stage_data *entry) -{ - int clean_merge = 1; - unsigned o_mode = entry->stages[1].mode; - unsigned a_mode = entry->stages[2].mode; - unsigned b_mode = entry->stages[3].mode; - unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); - unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); - unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); - struct stat st; - entry->processed = 1; - if (entry->rename_df_conflict_info) { - struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info; - char *src; + if (entry->rename_conflict_info) { + struct rename_conflict_info *conflict_info = entry->rename_conflict_info; switch (conflict_info->rename_type) { case RENAME_NORMAL: + case RENAME_ONE_FILE_TO_ONE: clean_merge = merge_content(o, path, o_sha, o_mode, a_sha, a_mode, b_sha, b_mode, - conflict_info->branch1); + conflict_info); break; case RENAME_DELETE: clean_merge = 0; @@ -1407,39 +1707,39 @@ static int process_df_entry(struct merge_options *o, conflict_info->branch2); break; case RENAME_ONE_FILE_TO_TWO: - src = conflict_info->pair1->one->path; clean_merge = 0; - output(o, 1, "CONFLICT (rename/rename): " - "Rename \"%s\"->\"%s\" in branch \"%s\" " - "rename \"%s\"->\"%s\" in \"%s\"%s", - src, conflict_info->pair1->two->path, conflict_info->branch1, - src, conflict_info->pair2->two->path, conflict_info->branch2, - o->call_depth ? " (left unresolved)" : ""); - if (o->call_depth) { - remove_file_from_cache(src); - update_file(o, 0, conflict_info->pair1->one->sha1, - conflict_info->pair1->one->mode, src); - } - conflict_rename_rename_1to2(o, conflict_info->pair1, - conflict_info->branch1, - conflict_info->pair2, - conflict_info->branch2); - conflict_info->dst_entry2->processed = 1; + conflict_rename_rename_1to2(o, conflict_info); + break; + case RENAME_TWO_FILES_TO_ONE: + clean_merge = 0; + conflict_rename_rename_2to1(o, conflict_info); break; default: entry->processed = 0; break; } } else if (o_sha && (!a_sha || !b_sha)) { - /* Modify/delete; deleted side may have put a directory in the way */ - const char *new_path = path; - if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) - new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2); - clean_merge = 0; - handle_delete_modify(o, path, new_path, - a_sha, a_mode, b_sha, b_mode); - } else if (!o_sha && !!a_sha != !!b_sha) { - /* directory -> (directory, file) */ + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) || + (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output(o, 2, "Removing %s", path); + /* do not touch working file if it did not exist */ + remove_file(o, 1, path, !a_sha); + } else { + /* Modify/delete; deleted side may have put a directory in the way */ + clean_merge = 0; + handle_modify_delete(o, path, o_sha, o_mode, + a_sha, a_mode, b_sha, b_mode); + } + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + /* [nothing|directory] -> ([nothing|directory], file) */ + const char *add_branch; const char *other_branch; unsigned mode; @@ -1459,21 +1759,37 @@ static int process_df_entry(struct merge_options *o, sha = b_sha; conf = "directory/file"; } - if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) { - const char *new_path = unique_path(o, path, add_branch); + if (dir_in_way(path, !o->call_depth)) { + char *new_path = unique_path(o, path, add_branch); clean_merge = 0; output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); + if (o->call_depth) + remove_file_from_cache(path); update_file(o, 0, sha, mode, new_path); + if (o->call_depth) + remove_file_from_cache(path); + free(new_path); } else { output(o, 2, "Adding %s", path); - update_file(o, 1, sha, mode, path); + /* do not overwrite file if already present */ + update_file_flags(o, sha, mode, path, 1, !a_sha); } - } else { - entry->processed = 0; - return 1; /* not handled; assume clean until processed */ - } + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ + /* case D: Modified in both, but differently. */ + clean_merge = merge_content(o, path, + o_sha, o_mode, a_sha, a_mode, b_sha, b_mode, + NULL); + } else if (!o_sha && !a_sha && !b_sha) { + /* + * this entry was deleted altogether. a_mode == 0 means + * we had that path and want to actively remove it. + */ + remove_file(o, 1, path, !a_mode); + } else + die("Fatal merge failure, shouldn't happen."); return clean_merge; } @@ -1517,11 +1833,11 @@ int merge_trees(struct merge_options *o, get_files_dirs(o, merge); entries = get_unmerged(); - make_room_for_directories_of_df_conflicts(o, entries); + record_df_conflict_files(o, entries); re_head = get_renames(o, head, common, head, merge, entries); re_merge = get_renames(o, merge, common, head, merge, entries); clean = process_renames(o, re_head, re_merge); - for (i = 0; i < entries->nr; i++) { + for (i = entries->nr-1; 0 <= i; i--) { const char *path = entries->items[i].string; struct stage_data *e = entries->items[i].util; if (!e->processed @@ -1529,13 +1845,6 @@ int merge_trees(struct merge_options *o, clean = 0; } for (i = 0; i < entries->nr; i++) { - const char *path = entries->items[i].string; - struct stage_data *e = entries->items[i].util; - if (!e->processed - && !process_df_entry(o, path, e)) - clean = 0; - } - for (i = 0; i < entries->nr; i++) { struct stage_data *e = entries->items[i].util; if (!e->processed) die("Unprocessed path??? %s", @@ -1741,6 +2050,8 @@ void init_merge_options(struct merge_options *o) o->current_file_set.strdup_strings = 1; memset(&o->current_directory_set, 0, sizeof(struct string_list)); o->current_directory_set.strdup_strings = 1; + memset(&o->df_conflict_file_set, 0, sizeof(struct string_list)); + o->df_conflict_file_set.strdup_strings = 1; } int parse_merge_opt(struct merge_options *o, const char *s) diff --git a/merge-recursive.h b/merge-recursive.h index 7e1e972b13..58f3435e9e 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -26,6 +26,7 @@ struct merge_options { struct strbuf obuf; struct string_list current_file_set; struct string_list current_directory_set; + struct string_list df_conflict_file_set; }; /* merge_trees() but with recursive ancestor consolidation */ @@ -11,8 +11,6 @@ * something different on Windows. */ -static int spawned_pager; - #ifndef WIN32 static void pager_preexec(void) { @@ -78,7 +76,7 @@ void setup_pager(void) if (!pager) return; - spawned_pager = 1; /* means we are emitting to terminal */ + setenv("GIT_PAGER_IN_USE", "true", 1); /* spawn the pager */ pager_argv[0] = pager; @@ -109,10 +107,6 @@ void setup_pager(void) int pager_in_use(void) { const char *env; - - if (spawned_pager) - return 1; - env = getenv("GIT_PAGER_IN_USE"); return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0; } diff --git a/parse-options-cb.c b/parse-options-cb.c index c248f667c1..6db0921fc1 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -40,7 +40,7 @@ int parse_opt_color_flag_cb(const struct option *opt, const char *arg, if (!arg) arg = unset ? "never" : (const char *)opt->defval; - value = git_config_colorbool(NULL, arg, -1); + value = git_config_colorbool(NULL, arg); if (value < 0) return opterror(opt, "expects \"always\", \"auto\", or \"never\"", 0); @@ -139,7 +139,7 @@ char *git_path_submodule(const char *path, const char *fmt, ...) strbuf_addch(&buf, '/'); strbuf_addstr(&buf, ".git"); - git_dir = read_gitfile_gently(buf.buf); + git_dir = read_gitfile(buf.buf); if (git_dir) { strbuf_reset(&buf); strbuf_addstr(&buf, git_dir); @@ -451,7 +451,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re memcpy(gitdir + len, "/.git", 6); len += 5; - tmp = read_gitfile_gently(gitdir); + tmp = read_gitfile(gitdir); if (tmp) { free(gitdir); len = strlen(tmp); @@ -862,7 +862,7 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) static inline int bad_ref_char(int ch) { - if (((unsigned) ch) <= ' ' || + if (((unsigned) ch) <= ' ' || ch == 0x7f || ch == '~' || ch == '^' || ch == ':' || ch == '\\') return 1; /* 2.13 Pattern Matching Notation */ diff --git a/remote-curl.c b/remote-curl.c index 5798aa57b6..b8cf45a7dd 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -762,9 +762,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) argv[argc++] = "--thin"; if (options.dry_run) argv[argc++] = "--dry-run"; - if (options.verbosity < 0) - argv[argc++] = "--quiet"; - else if (options.verbosity > 1) + if (options.verbosity > 1) argv[argc++] = "--verbose"; argv[argc++] = url; for (i = 0; i < nr_spec; i++) @@ -379,7 +379,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) * Try to read the location of the git directory from the .git file, * return path to git directory if found. */ -const char *read_gitfile_gently(const char *path) +const char *read_gitfile(const char *path) { char *buf; char *dir; @@ -441,7 +441,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT); - gitfile = (char*)read_gitfile_gently(gitdirenv); + gitfile = (char*)read_gitfile(gitdirenv); if (gitfile) { gitfile = xstrdup(gitfile); gitdirenv = gitfile; @@ -665,7 +665,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) if (one_filesystem) current_device = get_device_or_die(".", NULL); for (;;) { - gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile) gitdirenv = gitfile = xstrdup(gitfile); else { diff --git a/sha1_file.c b/sha1_file.c index 44444ae8f4..e002056b85 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -380,7 +380,7 @@ void add_to_alternates_file(const char *reference) { struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR); - char *alt = mkpath("%s/objects\n", reference); + char *alt = mkpath("%s\n", reference); write_or_die(fd, alt, strlen(alt)); if (commit_lock_file(lock)) die("could not close alternates file"); @@ -30,10 +30,8 @@ void strbuf_init(struct strbuf *sb, size_t hint) { sb->alloc = sb->len = 0; sb->buf = strbuf_slopbuf; - if (hint) { + if (hint) strbuf_grow(sb, hint); - sb->buf[0] = '\0'; - } } void strbuf_release(struct strbuf *sb) @@ -65,12 +63,15 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc) void strbuf_grow(struct strbuf *sb, size_t extra) { + int new_buf = !sb->alloc; if (unsigned_add_overflows(extra, 1) || unsigned_add_overflows(sb->len, extra + 1)) die("you want to use way too much memory"); - if (!sb->alloc) + if (new_buf) sb->buf = NULL; ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc); + if (new_buf) + sb->buf[0] = '\0'; } void strbuf_trim(struct strbuf *sb) diff --git a/string-list.c b/string-list.c index 51681189e8..d9810aba42 100644 --- a/string-list.c +++ b/string-list.c @@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list, return unsorted_string_list_lookup(list, string) != NULL; } +void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util) +{ + if (list->strdup_strings) + free(list->items[i].string); + if (free_util) + free(list->items[i].util); + list->items[i] = list->items[list->nr-1]; + list->nr--; +} diff --git a/string-list.h b/string-list.h index bda6983983..0684cb73bf 100644 --- a/string-list.h +++ b/string-list.h @@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list); int unsorted_string_list_has_string(struct string_list *list, const char *string); struct string_list_item *unsorted_string_list_lookup(struct string_list *list, const char *string); +void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util); #endif /* STRING_LIST_H */ diff --git a/submodule.c b/submodule.c index 1ba9646d34..ad86534ba1 100644 --- a/submodule.c +++ b/submodule.c @@ -32,7 +32,7 @@ static int add_submodule_odb(const char *path) const char *git_dir; strbuf_addf(&objects_directory, "%s/.git", path); - git_dir = read_gitfile_gently(objects_directory.buf); + git_dir = read_gitfile(objects_directory.buf); if (git_dir) { strbuf_reset(&objects_directory); strbuf_addstr(&objects_directory, git_dir); @@ -308,6 +308,114 @@ void set_config_fetch_recurse_submodules(int value) config_fetch_recurse_submodules = value; } +static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +{ + return 1; +} + +static int submodule_needs_pushing(const char *path, const unsigned char sha1[20]) +{ + if (add_submodule_odb(path) || !lookup_commit_reference(sha1)) + return 0; + + if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { + struct child_process cp; + const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL}; + struct strbuf buf = STRBUF_INIT; + int needs_pushing = 0; + + argv[1] = sha1_to_hex(sha1); + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + cp.dir = path; + if (start_command(&cp)) + die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s", + sha1_to_hex(sha1), path); + if (strbuf_read(&buf, cp.out, 41)) + needs_pushing = 1; + finish_command(&cp); + close(cp.out); + strbuf_release(&buf); + return needs_pushing; + } + + return 0; +} + +static void collect_submodules_from_diff(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + int *needs_pushing = data; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (!S_ISGITLINK(p->two->mode)) + continue; + if (submodule_needs_pushing(p->two->path, p->two->sha1)) { + *needs_pushing = 1; + break; + } + } +} + + +static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing) +{ + const unsigned char (*parents)[20]; + unsigned int i, n; + struct rev_info rev; + + n = commit_list_count(parent); + parents = xmalloc(n * sizeof(*parents)); + + for (i = 0; i < n; i++) { + hashcpy((unsigned char *)(parents + i), parent->item->object.sha1); + parent = parent->next; + } + + init_revisions(&rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = collect_submodules_from_diff; + rev.diffopt.format_callback_data = needs_pushing; + diff_tree_combined(commit->object.sha1, parents, n, 1, &rev); + + free(parents); +} + +int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name) +{ + struct rev_info rev; + struct commit *commit; + const char *argv[] = {NULL, NULL, "--not", "NULL", NULL}; + int argc = ARRAY_SIZE(argv) - 1; + char *sha1_copy; + int needs_pushing = 0; + struct strbuf remotes_arg = STRBUF_INIT; + + strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name); + init_revisions(&rev, NULL); + sha1_copy = xstrdup(sha1_to_hex(new_sha1)); + argv[1] = sha1_copy; + argv[3] = remotes_arg.buf; + setup_revisions(argc, argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + while ((commit = get_revision(&rev)) && !needs_pushing) + commit_need_pushing(commit, commit->parents, &needs_pushing); + + free(sha1_copy); + strbuf_release(&remotes_arg); + + return needs_pushing; +} + static int is_submodule_commit_present(const char *path, unsigned char sha1[20]) { int is_present = 0; @@ -373,6 +481,10 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]) const char *argv[] = {NULL, NULL, "--not", "--all", NULL}; int argc = ARRAY_SIZE(argv) - 1; + /* No need to check if there are no submodules configured */ + if (!config_name_for_path.nr) + return; + init_revisions(&rev, NULL); argv[1] = xstrdup(sha1_to_hex(new_sha1)); setup_revisions(argc, argv, &rev, NULL); @@ -479,7 +591,7 @@ int fetch_populated_submodules(int num_options, const char **options, strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name); strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf); strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name); - git_dir = read_gitfile_gently(submodule_git_dir.buf); + git_dir = read_gitfile(submodule_git_dir.buf); if (!git_dir) git_dir = submodule_git_dir.buf; if (is_directory(git_dir)) { @@ -517,7 +629,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) const char *git_dir; strbuf_addf(&buf, "%s/.git", path); - git_dir = read_gitfile_gently(buf.buf); + git_dir = read_gitfile(buf.buf); if (!git_dir) git_dir = buf.buf; if (!is_directory(git_dir)) { diff --git a/submodule.h b/submodule.h index 5350b0d5a0..799c22d6c6 100644 --- a/submodule.h +++ b/submodule.h @@ -29,5 +29,6 @@ int fetch_populated_submodules(int num_options, const char **options, 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]); +int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name); #endif diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index b5d89a2250..2b962cfda7 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -25,6 +25,11 @@ else test_set_prereq SETFACL fi +if test -z "$LOGNAME" +then + LOGNAME=$USER +fi + check_perms_and_acl () { test -r "$1" && getfacl "$1" > actual && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index 1b0f82fa4c..ed4275afe3 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -18,6 +18,9 @@ invalid_ref 'foo' valid_ref 'foo/bar/baz' valid_ref 'refs///heads/foo' invalid_ref 'heads/foo/' +valid_ref '/heads/foo' +valid_ref '///heads/foo' +invalid_ref '/foo' invalid_ref './foo' invalid_ref '.refs/foo' invalid_ref 'heads/foo..bar' @@ -27,6 +30,9 @@ invalid_ref 'heads/foo.lock' valid_ref 'heads/foo@bar' invalid_ref 'heads/v@{ation' invalid_ref 'heads/foo\bar' +invalid_ref "$(printf 'heads/foo\t')" +invalid_ref "$(printf 'heads/foo\177')" +valid_ref "$(printf 'heads/fu\303\237')" test_expect_success "check-ref-format --branch @{-1}" ' T=$(git write-tree) && @@ -70,7 +76,10 @@ invalid_ref_normalized() { valid_ref_normalized 'heads/foo' 'heads/foo' valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo' +valid_ref_normalized '/heads/foo' 'heads/foo' +valid_ref_normalized '///heads/foo' 'heads/foo' invalid_ref_normalized 'foo' +invalid_ref_normalized '/foo' invalid_ref_normalized 'heads/foo/../bar' invalid_ref_normalized 'heads/./foo' invalid_ref_normalized 'heads\foo' diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index bb01d5ab8f..523ce9c45b 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -110,6 +110,42 @@ test_expect_success 'email with embedded > is not okay' ' grep "error in commit $new" out ' +test_expect_success 'missing < email delimiter is reported nicely' ' + git cat-file commit HEAD >basis && + sed "s/<//" basis >bad-email-2 && + new=$(git hash-object -t commit -w --stdin <bad-email-2) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + git fsck 2>out && + cat out && + grep "error in commit $new.* - bad name" out +' + +test_expect_success 'missing email is reported nicely' ' + git cat-file commit HEAD >basis && + sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 && + new=$(git hash-object -t commit -w --stdin <bad-email-3) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + git fsck 2>out && + cat out && + grep "error in commit $new.* - missing email" out +' + +test_expect_success '> in name is reported' ' + git cat-file commit HEAD >basis && + sed "s/ </> </" basis >bad-email-4 && + new=$(git hash-object -t commit -w --stdin <bad-email-4) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + git fsck 2>out && + cat out && + grep "error in commit $new" out +' + test_expect_success 'tag pointing to nonexistent' ' cat >invalid-tag <<-\EOF && object ffffffffffffffffffffffffffffffffffffffff diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index a42e03967b..75874e85df 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -118,6 +118,15 @@ test_expect_success 'checkout -b to an existing branch fails' ' test_must_fail do_checkout branch2 $HEAD2 ' +test_expect_success 'checkout -b to @{-1} fails with the right branch name' ' + git reset --hard HEAD && + git checkout branch1 && + git checkout branch2 && + echo >expect "fatal: A branch named '\''branch1'\'' already exists." && + test_must_fail git checkout -b @{-1} 2>actual && + test_cmp expect actual +' + test_expect_success 'checkout -B to an existing branch resets branch to HEAD' ' git checkout branch1 && @@ -180,4 +189,12 @@ test_expect_success 'checkout -b <describe>' ' test_cmp expect actual ' +test_expect_success 'checkout -B to the current branch fails before merging' ' + git checkout branch1 && + setup_dirty_mergeable && + git commit -mfooble && + test_must_fail git checkout -B branch1 initial && + test_must_fail test_dirty_mergeable +' + test_done diff --git a/t/t3005-ls-files-relative.sh b/t/t3005-ls-files-relative.sh index a2b63e2c10..377869432e 100755 --- a/t/t3005-ls-files-relative.sh +++ b/t/t3005-ls-files-relative.sh @@ -45,11 +45,12 @@ test_expect_success 'ls-files -c' ' for f in ../y* do echo "error: pathspec $sq$f$sq did not match any file(s) known to git." - done >expect && - echo "Did you forget to ${sq}git add${sq}?" >>expect && - ls ../x* >>expect && - test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual 2>&1 && - test_cmp expect actual + done >expect.err && + echo "Did you forget to ${sq}git add${sq}?" >>expect.err && + ls ../x* >expect.out && + test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual.out 2>actual.err && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err ) ' @@ -59,11 +60,12 @@ test_expect_success 'ls-files -o' ' for f in ../x* do echo "error: pathspec $sq$f$sq did not match any file(s) known to git." - done >expect && - echo "Did you forget to ${sq}git add${sq}?" >>expect && - ls ../y* >>expect && - test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual 2>&1 && - test_cmp expect actual + done >expect.err && + echo "Did you forget to ${sq}git add${sq}?" >>expect.err && + ls ../y* >expect.out && + test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual.out 2>actual.err && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err ) ' diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 0c02d56952..55ef1895d7 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -267,7 +267,8 @@ test_expect_success 'setup 8' ' ln -s e a && git add a e && test_tick && - git commit -m "rename a->e, symlink a->e" + git commit -m "rename a->e, symlink a->e" && + oln=`printf e | git hash-object --stdin` fi ' @@ -630,16 +631,18 @@ test_expect_success 'merge-recursive copy vs. rename' ' if test_have_prereq SYMLINKS then - test_expect_success 'merge-recursive rename vs. rename/symlink' ' + test_expect_failure 'merge-recursive rename vs. rename/symlink' ' git checkout -f rename && git merge rename-ln && ( git ls-tree -r HEAD ; git ls-files -s ) >actual && ( + echo "120000 blob $oln a" echo "100644 blob $o0 b" echo "100644 blob $o0 c" echo "100644 blob $o0 d/e" echo "100644 blob $o0 e" + echo "120000 $oln 0 a" echo "100644 $o0 0 b" echo "100644 $o0 0 c" echo "100644 $o0 0 d/e" diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 9e69c8c926..7633930bb4 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -98,6 +98,18 @@ test_expect_success 'git branch -m q r/q should fail when r exists' ' test_must_fail git branch -m q r/q ' +test_expect_success 'git branch -M foo bar should fail when bar is checked out' ' + git branch bar && + git checkout -b foo && + test_must_fail git branch -M bar foo +' + +test_expect_success 'git branch -M baz bam should succeed when baz is checked out' ' + git checkout -b baz && + git branch bam && + git branch -M baz bam +' + mv .git/config .git/config-saved test_expect_success 'git branch -m q q2 without config should succeed' ' @@ -542,4 +554,17 @@ test_expect_success 'attempt to delete a branch merged to its base' ' test_must_fail git branch -d my10 ' +test_expect_success 'use set-upstream on the current branch' ' + git checkout master && + git --bare init myupstream.git && + git push myupstream.git master:refs/heads/frotz && + git remote add origin myupstream.git && + git fetch && + git branch --set-upstream master origin/frotz && + + test "z$(git config branch.master.remote)" = "zorigin" && + test "z$(git config branch.master.merge)" = "zrefs/heads/frotz" + +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 8538813d1d..b981572d73 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -527,6 +527,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' ' git rebase --abort ' +test_expect_success 'clean error after failed "exec"' ' + test_tick && + test_when_finished "git rebase --abort || :" && + ( + FAKE_LINES="1 exec_false" && + export FAKE_LINES && + test_must_fail git rebase -i HEAD^ + ) && + echo "edited again" > file7 && + git add file7 && + test_must_fail git rebase --continue 2>error && + grep "You have staged changes in your working tree." error +' + test_expect_success 'rebase a detached HEAD' ' grandparent=$(git rev-parse HEAD~2) && git checkout $(git rev-parse HEAD) && diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7197aae1ed..fcdb18217a 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -542,7 +542,7 @@ test_expect_success 'ref with non-existent reflog' ' echo bar6 > file2 && git add file2 && git stash && - ! "git rev-parse --quiet --verify does-not-exist" && + test_must_fail git rev-parse --quiet --verify does-not-exist && test_must_fail git stash drop does-not-exist && test_must_fail git stash drop does-not-exist@{0} && test_must_fail git stash pop does-not-exist && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 92248d24c4..67975129bc 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -179,12 +179,21 @@ test_expect_success 'configuration To: header' ' grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9 ' +# check_patch <patch>: Verify that <patch> looks like a half-sane +# patch email to avoid a false positive with !grep +check_patch () { + grep -e "^From:" "$1" && + grep -e "^Date:" "$1" && + grep -e "^Subject:" "$1" +} + test_expect_success '--no-to overrides config.to' ' git config --replace-all format.to \ "R. E. Cipient <rcipient@example.com>" && git format-patch --no-to --stdout master..side | sed -e "/^\$/q" >patch10 && + check_patch patch10 && ! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10 ' @@ -195,6 +204,7 @@ test_expect_success '--no-to and --to replaces config.to' ' git format-patch --no-to --to="Someone Else <else@out.there>" \ --stdout master..side | sed -e "/^\$/q" >patch11 && + check_patch patch11 && ! grep "^To: Someone <someone@out.there>\$" patch11 && grep "^To: Someone Else <else@out.there>\$" patch11 ' @@ -205,15 +215,17 @@ test_expect_success '--no-cc overrides config.cc' ' "C. E. Cipient <rcipient@example.com>" && git format-patch --no-cc --stdout master..side | sed -e "/^\$/q" >patch12 && + check_patch patch12 && ! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12 ' -test_expect_success '--no-add-headers overrides config.headers' ' +test_expect_success '--no-add-header overrides config.headers' ' git config --replace-all format.headers \ "Header1: B. E. Cipient <rcipient@example.com>" && - git format-patch --no-add-headers --stdout master..side | + git format-patch --no-add-header --stdout master..side | sed -e "/^\$/q" >patch13 && + check_patch patch13 && ! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13 ' @@ -445,22 +457,22 @@ test_expect_success 'thread deep cover-letter in-reply-to' ' ' test_expect_success 'thread via config' ' - git config format.thread true && + test_config format.thread true && check_threading expect.thread master ' test_expect_success 'thread deep via config' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.deep master ' test_expect_success 'thread config + override' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.thread --thread master ' test_expect_success 'thread config + --no-thread' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.no-threading --no-thread master ' @@ -480,6 +492,7 @@ test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && git format-patch --cover-letter -1 && + check_patch 0000-cover-letter.patch && ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch && git format-patch --cover-letter -1 -M && grep "file => foo .* 0 *\$" 0000-cover-letter.patch @@ -657,6 +670,7 @@ test_expect_success 'format-patch --no-signature ignores format.signature' ' git config format.signature "config sig" && git format-patch --stdout --signature="my sig" --no-signature \ -1 >output && + check_patch output && ! grep "config sig" output && ! grep "my sig" output && ! grep "^-- \$" output @@ -673,17 +687,20 @@ test_expect_success 'format-patch --signature --cover-letter' ' test_expect_success 'format.signature="" supresses signatures' ' git config format.signature "" && git format-patch --stdout -1 >output && + check_patch output && ! grep "^-- \$" output ' test_expect_success 'format-patch --no-signature supresses signatures' ' git config --unset-all format.signature && git format-patch --stdout --no-signature -1 >output && + check_patch output && ! grep "^-- \$" output ' test_expect_success 'format-patch --signature="" supresses signatures' ' - git format-patch --signature="" -1 >output && + git format-patch --stdout --signature="" -1 >output && + check_patch output && ! grep "^-- \$" output ' @@ -869,4 +886,12 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +test_expect_success 'format patch ignores color.ui' ' + test_unconfig color.ui && + git format-patch --stdout -1 >expect && + test_config color.ui always && + git format-patch --stdout -1 >actual && + test_cmp expect actual +' + test_done diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index faa2e96337..30bec4b5f9 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -32,4 +32,91 @@ test_expect_success push ' ) ' +test_expect_success 'push if submodule has no remote' ' + ( + cd work/gar/bage && + >junk2 && + git add junk2 && + git commit -m "Second junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Second commit for gar/bage" && + git push --recurse-submodules=check ../pub.git master + ) +' + +test_expect_success 'push fails if submodule commit not on remote' ' + ( + cd work/gar && + git clone --bare bage ../../submodule.git && + cd bage && + git remote add origin ../../../submodule.git && + git fetch && + >junk3 && + git add junk3 && + git commit -m "Third junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Third commit for gar/bage" && + test_must_fail git push --recurse-submodules=check ../pub.git master + ) +' + +test_expect_success 'push succeeds after commit was pushed to remote' ' + ( + cd work/gar/bage && + git push origin master + ) && + ( + cd work && + git push --recurse-submodules=check ../pub.git master + ) +' + +test_expect_success 'push fails when commit on multiple branches if one branch has no remote' ' + ( + cd work/gar/bage && + >junk4 && + git add junk4 && + git commit -m "Fourth junk" + ) && + ( + cd work && + git branch branch2 && + git add gar/bage && + git commit -m "Fourth commit for gar/bage" && + git checkout branch2 && + ( + cd gar/bage && + git checkout HEAD~1 + ) && + >junk1 && + git add junk1 && + git commit -m "First junk" && + test_must_fail git push --recurse-submodules=check ../pub.git + ) +' + +test_expect_success 'push succeeds if submodule has no remote and is on the first superproject commit' ' + git init --bare a + git clone a a1 && + ( + cd a1 && + git init b + ( + cd b && + >junk && + git add junk && + git commit -m "initial" + ) && + git add b && + git commit -m "added submodule" && + git push --recurse-submodule=check origin master + ) +' + test_done diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index a266ca5636..64767d8705 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -132,8 +132,12 @@ x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1" x40="$x38$x2" test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' ' - sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log | - grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] " + sed \ + -e "s/PUT /OP /" \ + -e "s/MOVE /OP /" \ + -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \ + "$HTTPD_ROOT_PATH"/access.log | + grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] " ' diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 151ea531bd..e8103144bb 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -202,9 +202,36 @@ test_expect_success 'clone separate gitdir: output' ' test_cmp expected dst/.git ' +test_expect_success 'clone from .git file' ' + git clone dst/.git dst2 +' + test_expect_success 'clone separate gitdir where target already exists' ' rm -rf dst && test_must_fail git clone --separate-git-dir realgitdir src dst ' +test_expect_success 'clone --reference from original' ' + git clone --shared --bare src src-1 && + git clone --bare src src-2 && + git clone --reference=src-2 --bare src-1 target-8 && + grep /src-2/ target-8/objects/info/alternates +' + +test_expect_success 'clone with more than one --reference' ' + git clone --bare src src-3 && + git clone --bare src src-4 && + git clone --reference=src-3 --reference=src-4 src target-9 && + grep /src-3/ target-9/.git/objects/info/alternates && + grep /src-4/ target-9/.git/objects/info/alternates +' + +test_expect_success 'clone from original with relative alternate' ' + mkdir nest && + git clone --bare src nest/src-5 && + echo ../../../src/.git/objects >nest/src-5/objects/info/alternates && + git clone --bare nest/src-5 target-10 && + grep /src/\\.git/objects target-10/objects/info/alternates +' + test_done diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index eec8f4e3ed..27c3d73961 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' ' git add letters && git commit -m initial && + # Throw in letters.txt for sorting order fun + # ("letters.txt" sorts between "letters" and "letters/file") echo i >>letters && - git add letters && + echo "version 2" >letters.txt && + git add letters letters.txt && git commit -m modified && git checkout -b delete HEAD^ && git rm letters && mkdir letters && >letters/file && - git add letters && + echo "version 1" >letters.txt && + git add letters letters.txt && git commit -m deleted ' @@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' ' git checkout delete^0 && test_must_fail git merge modify && - test 3 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 1 = $(git ls-files -o | wc -l) && + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && test -f letters/file && + test -f letters.txt && test -f letters~modify ' test_expect_success 'modify/delete + directory/file conflict; other way' ' + # Yes, we really need the double reset since "letters" appears as + # both a file and a directory. + git reset --hard && git reset --hard && git clean -f && git checkout modify^0 && + test_must_fail git merge delete && - test 3 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 1 = $(git ls-files -o | wc -l) && + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && test -f letters/file && + test -f letters.txt && test -f letters~HEAD ' diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 1ed259d864..9d8584e957 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -252,6 +252,7 @@ test_expect_success 'setup for rename + d/f conflicts' ' git reset --hard && git checkout --orphan dir-in-way && git rm -rf . && + git clean -fdqx && mkdir sub && mkdir dir && @@ -302,11 +303,11 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' ' git checkout -q renamed-file-has-no-conflicts^0 && test_must_fail git merge --strategy=recursive dir-in-way >output && - grep "CONFLICT (delete/modify): dir/file-in-the-way" output && + grep "CONFLICT (modify/delete): dir/file-in-the-way" output && grep "Auto-merging dir" output && grep "Adding as dir~HEAD instead" output && - test 2 -eq "$(git ls-files -u | wc -l)" && + test 3 -eq "$(git ls-files -u | wc -l)" && test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && @@ -324,11 +325,11 @@ test_expect_success 'Same as previous, but merged other way' ' test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors && ! grep "error: refusing to lose untracked file at" errors && - grep "CONFLICT (delete/modify): dir/file-in-the-way" output && + grep "CONFLICT (modify/delete): dir/file-in-the-way" output && grep "Auto-merging dir" output && grep "Adding as dir~renamed-file-has-no-conflicts instead" output && - test 2 -eq "$(git ls-files -u | wc -l)" && + test 3 -eq "$(git ls-files -u | wc -l)" && test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && @@ -350,11 +351,11 @@ cat >expected <<\EOF && 8 9 10 -<<<<<<< HEAD +<<<<<<< HEAD:dir 12 ======= 11 ->>>>>>> dir-not-in-way +>>>>>>> dir-not-in-way:sub/file EOF test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' ' @@ -404,11 +405,11 @@ cat >expected <<\EOF && 8 9 10 -<<<<<<< HEAD +<<<<<<< HEAD:sub/file 11 ======= 12 ->>>>>>> renamed-file-has-conflicts +>>>>>>> renamed-file-has-conflicts:dir EOF test_expect_success 'Same as previous, but merged other way' ' @@ -609,4 +610,278 @@ test_expect_success 'check handling of differently renamed file with D/F conflic ! test -f original ' +test_expect_success 'setup avoid unnecessary update, normal rename' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-1 && + git rm -rf . && + git clean -fdqx && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original && + git add -A && + git commit -m "Common commmit" && + + git mv original rename && + echo 11 >>rename && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-1 HEAD~1 && + echo "random content" >random-file && + git add -A && + git commit -m "Random, unrelated changes" +' + +test_expect_success 'avoid unnecessary update, normal rename' ' + git checkout -q avoid-unnecessary-update-1^0 && + test-chmtime =1000000000 rename && + test-chmtime -v +0 rename >expect && + git merge merge-branch-1 && + test-chmtime -v +0 rename >actual && + test_cmp expect actual # "rename" should have stayed intact +' + +test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-2 && + git rm -rf . && + git clean -fdqx && + + mkdir df && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file && + git add -A && + git commit -m "Common commmit" && + + git mv df/file temp && + rm -rf df && + git mv temp df && + echo 11 >>df && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-2 HEAD~1 && + >unrelated-change && + git add unrelated-change && + git commit -m "Only unrelated changes" +' + +test_expect_success 'avoid unnecessary update, with D/F conflict' ' + git checkout -q avoid-unnecessary-update-2^0 && + test-chmtime =1000000000 df && + test-chmtime -v +0 df >expect && + git merge merge-branch-2 && + test-chmtime -v +0 df >actual && + test_cmp expect actual # "df" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >irrelevant && + mkdir df && + >df/file && + git add -A && + git commit -mA && + + git checkout -b side + git rm -rf df && + git commit -mB && + + git checkout master && + git rm -rf df && + echo bla >df && + git add -A && + git commit -m "Add a newfile" +' + +test_expect_success 'avoid unnecessary update, dir->(file,nothing)' ' + git checkout -q master^0 && + test-chmtime =1000000000 df && + test-chmtime -v +0 df >expect && + git merge side && + test-chmtime -v +0 df >actual && + test_cmp expect actual # "df" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, modify/delete' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >irrelevant && + >file && + git add -A && + git commit -mA && + + git checkout -b side + git rm -f file && + git commit -m "Delete file" && + + git checkout master && + echo bla >file && + git add -A && + git commit -m "Modify file" +' + +test_expect_success 'avoid unnecessary update, modify/delete' ' + git checkout -q master^0 && + test-chmtime =1000000000 file && + test-chmtime -v +0 file >expect && + test_must_fail git merge side && + test-chmtime -v +0 file >actual && + test_cmp expect actual # "file" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, rename/add-dest' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file && + git add -A && + git commit -mA && + + git checkout -b side + cp file newfile && + git add -A && + git commit -m "Add file copy" && + + git checkout master && + git mv file newfile && + git commit -m "Rename file" +' + +test_expect_success 'avoid unnecessary update, rename/add-dest' ' + git checkout -q master^0 && + test-chmtime =1000000000 newfile && + test-chmtime -v +0 newfile >expect && + git merge side && + test-chmtime -v +0 newfile >actual && + test_cmp expect actual # "file" should have stayed intact +' + +test_expect_success 'setup merge of rename + small change' ' + git reset --hard && + git checkout --orphan rename-plus-small-change && + git rm -rf . && + git clean -fdqx && + + echo ORIGINAL >file && + git add file && + + test_tick && + git commit -m Initial && + git checkout -b rename_branch && + git mv file renamed_file && + git commit -m Rename && + git checkout rename-plus-small-change && + echo NEW-VERSION >file && + git commit -a -m Reformat +' + +test_expect_success 'merge rename + small change' ' + git merge rename_branch && + + test 1 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file) +' + +test_expect_success 'setup for use of extended merge markers' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && + git add original_file && + git commit -mA && + + git checkout -b rename && + echo 9 >>original_file && + git add original_file && + git mv original_file renamed_file && + git commit -mB && + + git checkout master && + echo 8.5 >>original_file && + git add original_file && + git commit -mC +' + +cat >expected <<\EOF && +1 +2 +3 +4 +5 +6 +7 +8 +<<<<<<< HEAD:renamed_file +9 +======= +8.5 +>>>>>>> master^0:original_file +EOF + +test_expect_success 'merge master into rename has correct extended markers' ' + git checkout rename^0 && + test_must_fail git merge -s recursive master^0 && + test_cmp expected renamed_file +' + +cat >expected <<\EOF && +1 +2 +3 +4 +5 +6 +7 +8 +<<<<<<< HEAD:original_file +8.5 +======= +9 +>>>>>>> rename^0:renamed_file +EOF + +test_expect_success 'merge rename into master has correct extended markers' ' + git reset --hard && + git checkout master^0 && + test_must_fail git merge -s recursive rename^0 && + test_cmp expected renamed_file +' + +test_expect_success 'setup spurious "refusing to lose untracked" message' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + > irrelevant_file && + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && + git add irrelevant_file original_file && + git commit -mA && + + git checkout -b rename && + git mv original_file renamed_file && + git commit -mB && + + git checkout master && + git rm original_file && + git commit -mC +' + +test_expect_success 'no spurious "refusing to lose untracked" message' ' + git checkout master^0 && + test_must_fail git merge rename^0 2>errors.txt && + ! grep "refusing to lose untracked file" errors.txt +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 62125eca81..c6f1f9f8ab 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -732,7 +732,7 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' " git bisect reset && git checkout broken && git bisect start broken master --no-checkout && - git bisect run sh -c ' + git bisect run \"\$SHELL_PATH\" -c ' GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) && git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ && git pack-objects --stdout >/dev/null < tmp.\$\$ diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 871577d90c..dfee7d159b 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -1,9 +1,15 @@ #!/bin/sh -test_description='recursive merge corner cases' +test_description='recursive merge corner cases involving criss-cross merges' . ./test-lib.sh +get_clean_checkout () { + git reset --hard && + git clean -fdqx && + git checkout "$1" +} + # # L1 L2 # o---o @@ -51,23 +57,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' test_must_fail git merge -s recursive R2^0 && - test 5 = $(git ls-files -s | wc -l) && - test 3 = $(git ls-files -u | wc -l) && - test 0 = $(git ls-files -o | wc -l) && + test 2 = $(git ls-files -s | wc -l) && + test 2 = $(git ls-files -u | wc -l) && + test 2 = $(git ls-files -o | wc -l) && - test $(git rev-parse :0:one) = $(git rev-parse L2:one) && - test $(git rev-parse :0:two) = $(git rev-parse R2:two) && test $(git rev-parse :2:three) = $(git rev-parse L2:three) && test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - cp two merged && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ - -L "" \ - -L "Temporary merge branch 1" \ - merged empty one && - test $(git rev-parse :1:three) = $(git hash-object merged) + test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && + test $(git rev-parse R2:three) = $(git hash-object three~R2^0) ' # @@ -126,24 +124,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' test_must_fail git merge -s recursive R2^0 && - test 5 = $(git ls-files -s | wc -l) && - test 3 = $(git ls-files -u | wc -l) && - test 0 = $(git ls-files -o | wc -l) && + test 2 = $(git ls-files -s | wc -l) && + test 2 = $(git ls-files -u | wc -l) && + test 2 = $(git ls-files -o | wc -l) && - test $(git rev-parse :0:one) = $(git rev-parse L2:one) && - test $(git rev-parse :0:two) = $(git rev-parse R2:two) && test $(git rev-parse :2:three) = $(git rev-parse L2:three) && test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - head -n 10 two >merged && - cp one merge-me && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ - -L "" \ - -L "Temporary merge branch 1" \ - merged empty merge-me && - test $(git rev-parse :1:three) = $(git hash-object merged) + test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && + test $(git rev-parse R2:three) = $(git hash-object three~R2^0) ' # @@ -231,4 +220,557 @@ test_expect_success 'git detects differently handled merges conflict' ' test $(git rev-parse :1:new_a) = $(git hash-object merged) ' +# +# criss-cross + modify/delete: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: file with contents 'A\n' +# Commit B: file with contents 'B\n' +# Commit C: file not present +# Commit D: file with contents 'B\n' +# Commit E: file not present +# +# Merging commits D & E should result in modify/delete conflict. + +test_expect_success 'setup criss-cross + modify/delete resolved differently' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + git rm file && + test_tick && + git commit -m C && + + git checkout B && + echo B >file && + git add file && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + echo B >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git rm file && + test_tick && + git commit -m E && + git tag E +' + +test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u | wc -l) && + + test $(git rev-parse :1:file) = $(git rev-parse master:file) && + test $(git rev-parse :2:file) = $(git rev-parse B:file) +' + +test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' + git reset --hard && + git checkout E^0 && + + test_must_fail git merge -s recursive D^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u | wc -l) && + + test $(git rev-parse :1:file) = $(git rev-parse master:file) && + test $(git rev-parse :3:file) = $(git rev-parse B:file) +' + +# +# criss-cross + modify/modify with very contrived file contents: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: file with contents 'A\n' +# Commit B: file with contents 'B\n' +# Commit C: file with contents 'C\n' +# Commit D: file with contents 'D\n' +# Commit E: file with contents: +# <<<<<<< Temporary merge branch 1 +# C +# ======= +# B +# >>>>>>> Temporary merge branch 2 +# +# Now, when we merge commits D & E, does git detect the conflict? + +test_expect_success 'setup differently handled merges of content conflict' ' + git clean -fdqx && + rm -rf .git && + git init && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + echo C >file && + git add file && + test_tick && + git commit -m C && + + git checkout B && + echo B >file && + git add file && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + echo D >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + cat <<EOF >file && +<<<<<<< Temporary merge branch 1 +C +======= +B +>>>>>>> Temporary merge branch 2 +EOF + git add file && + test_tick && + git commit -m E && + git tag E +' + +test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' ' + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + test 3 -eq $(git ls-files -s | wc -l) && + test 3 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :2:file) = $(git rev-parse D:file) && + test $(git rev-parse :3:file) = $(git rev-parse E:file) +' + +# +# criss-cross + d/f conflict via add/add: +# Commit A: Neither file 'a' nor directory 'a/' exist. +# Commit B: Introduce 'a' +# Commit C: Introduce 'a/file' +# Commit D: Merge B & C, keeping 'a' and deleting 'a/' +# +# Two different later cases: +# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file' +# Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file' +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E1 or E2 +# +# Merging D & E1 requires we first create a virtual merge base X from +# merging A & B in memory. Now, if X could keep both 'a' and 'a/file' in +# the index, then the merge of D & E1 could be resolved cleanly with both +# 'a' and 'a/file' removed. Since git does not currently allow creating +# such a tree, the best we can do is have X contain both 'a~<unique>' and +# 'a/file' resulting in the merge of D and E1 having a rename/delete +# conflict for 'a'. (Although this merge appears to be unsolvable with git +# currently, git could do a lot better than it currently does with these +# d/f conflicts, which is the purpose of this test.) +# +# Merge of D & E2 has similar issues for path 'a', but should always result +# in a modify/delete conflict for path 'a/file'. +# +# We run each merge in both directions, to check for directional issues +# with D/F conflict handling. +# + +test_expect_success 'setup differently handled merges of directory/file conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >ignore-me && + git add ignore-me && + test_tick && + git commit -m A && + git tag A && + + git branch B && + git checkout -b C && + mkdir a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m C && + + git checkout B && + echo 5 >a && + git add a && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + rm -rf a/ && + echo 5 >a && + git add a && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m E1 && + git tag E1 && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + printf "10\n11\n" >a/file && + git add a/file && + test_tick && + git commit -m E2 && + git tag E2 +' + +test_expect_success 'merge of D & E1 fails but has appropriate contents' ' + get_clean_checkout D^0 && + + test_must_fail git merge -s recursive E1^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 1 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && + test $(git rev-parse :2:a) = $(git rev-parse B:a) +' + +test_expect_success 'merge of E1 & D fails but has appropriate contents' ' + get_clean_checkout E1^0 && + + test_must_fail git merge -s recursive D^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 1 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && + test $(git rev-parse :3:a) = $(git rev-parse B:a) +' + +test_expect_success 'merge of D & E2 fails but has appropriate contents' ' + get_clean_checkout D^0 && + + test_must_fail git merge -s recursive E2^0 && + + test 4 -eq $(git ls-files -s | wc -l) && + test 3 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :2:a) = $(git rev-parse B:a) && + test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) && + test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && + test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && + + test -f a~HEAD +' + +test_expect_success 'merge of E2 & D fails but has appropriate contents' ' + get_clean_checkout E2^0 && + + test_must_fail git merge -s recursive D^0 && + + test 4 -eq $(git ls-files -s | wc -l) && + test 3 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :3:a) = $(git rev-parse B:a) && + test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) && + test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) + test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && + + test -f a~D^0 +' + +# +# criss-cross with rename/rename(1to2)/modify followed by +# rename/rename(2to1)/modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b, modifying by adding a line +# Commit C: rename a->c +# Commit D: merge B&C, resolving conflict by keeping contents in newname +# Commit E: merge B&C, resolving conflict similar to D but adding another line +# +# There is a conflict merging B & C, but one of filename not of file +# content. Whoever created D and E chose specific resolutions for that +# conflict resolution. Now, since: (1) there is no content conflict +# merging B & C, (2) D does not modify that merged content further, and (3) +# both D & E resolve the name conflict in the same way, the modification to +# newname in E should not cause any conflicts when it is merged with D. +# (Note that this can be accomplished by having the virtual merge base have +# the merged contents of b and c stored in a file named a, which seems like +# the most logical choice anyway.) +# +# Comment from Junio: I do not necessarily agree with the choice "a", but +# it feels sound to say "B and C do not agree what the final pathname +# should be, but we know this content was derived from the common A:a so we +# use one path whose name is arbitrary in the virtual merge base X between +# D and E" and then further let the rename detection to notice that that +# arbitrary path gets renamed between X-D to "newname" and X-E also to +# "newname" to resolve it as both sides renaming it to the same new +# name. It is akin to what we do at the content level, i.e. "B and C do not +# agree what the final contents should be, so we leave the conflict marker +# but that may cancel out at the final merge stage". + +test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' + git reset --hard && + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + echo 7 >>b && + git add -u && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout -q B^0 && + git merge --no-commit -s ours C^0 && + git mv b newname && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout -q C^0 && + git merge --no-commit -s ours B^0 && + git mv c newname && + printf "7\n8\n" >>newname && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E +' + +test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' + git checkout D^0 && + + git merge -s recursive E^0 && + + test 1 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) +' + +# +# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c, add different a +# Commit D: merge B&C, keeping b&c and (new) a modified at beginning +# Commit E: merge B&C, keeping b&c and (new) a modified at end +# +# Merging commits D & E should result in no conflict; doing so correctly +# requires getting the virtual merge base (from merging B&C) right, handling +# renaming carefully (both in the virtual merge base and later), and getting +# content merge handled. + +test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "lots\nof\nwords\nand\ncontent\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + printf "2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git checkout C -- a c && + mv a old_a && + echo 1 >a && + cat old_a >>a && + rm old_a && + git add -u && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout C^0 && + git merge --no-commit -s ours B^0 && + git checkout B -- b && + echo 8 >>a && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E +' + +test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' + git checkout D^0 && + + git merge -s recursive E^0 && + + test 3 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && + test $(git rev-parse HEAD:c) = $(git rev-parse A:a) && + test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")" +' + +# +# criss-cross with rename/rename(1to2)/add-dest + simple modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b, add c +# Commit C: rename a->c +# Commit D: merge B&C, keeping A:a and B:c +# Commit E: merge B&C, keeping A:a and slightly modified c from B +# +# Merging commits D & E should result in no conflict. The virtual merge +# base of B & C needs to not delete B:c for that to work, though... + +test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + printf "1\n2\n3\n4\n5\n6\n7\n" >c && + git add c && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + git commit -m "D is like B but renames b back to a" && + git tag D && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + echo 8 >>c && + git add c && + git commit -m "E like D but has mod in c" && + git tag E +' + +test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' + git checkout D^0 && + + git merge -s recursive E^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse HEAD:a) = $(git rev-parse A:a) && + test $(git rev-parse HEAD:c) = $(git rev-parse E:c) +' + test_done diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index a9b0ac1efc..19de5b16eb 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -110,4 +110,18 @@ test_expect_success '--set-upstream does not change branch' ' grep -q "^refs/heads/master$" actual && cmp expect2 actual2 ' + +test_expect_success '--set-upstream @{-1}' ' + git checkout from-master && + git checkout from-master2 && + git config branch.from-master2.merge > expect2 && + git branch --set-upstream @{-1} follower && + git config branch.from-master.merge > actual && + git config branch.from-master2.merge > actual2 && + git branch --set-upstream from-master follower && + git config branch.from-master.merge > expect && + test_cmp expect2 actual2 && + test_cmp expect actual +' + test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh new file mode 100755 index 0000000000..32591f9413 --- /dev/null +++ b/t/t6042-merge-rename-corner-cases.sh @@ -0,0 +1,578 @@ +#!/bin/sh + +test_description="recursive merge corner cases w/ renames but not criss-crosses" +# t6036 has corner cases that involve both criss-cross merges and renames + +. ./test-lib.sh + +test_expect_success 'setup rename/delete + untracked file' ' + echo "A pretty inscription" >ring && + git add ring && + test_tick && + git commit -m beginning && + + git branch people && + git checkout -b rename-the-ring && + git mv ring one-ring-to-rule-them-all && + test_tick && + git commit -m fullname && + + git checkout people && + git rm ring && + echo gollum >owner && + git add owner && + test_tick && + git commit -m track-people-instead-of-objects && + echo "Myyy PRECIOUSSS" >ring +' + +test_expect_success "Does git preserve Gollum's precious artifact?" ' + test_must_fail git merge -s recursive rename-the-ring && + + # Make sure git did not delete an untracked file + test -f ring +' + +# Testcase setup for rename/modify/add-source: +# Commit A: new file: a +# Commit B: modify a slightly +# Commit C: rename a->b, add completely different a +# +# We should be able to merge B & C cleanly + +test_expect_success 'setup rename/modify/add-source conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + echo 8 >>a && + git add a && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo something completely different >a && + git add a && + git commit -m C +' + +test_expect_failure 'rename/modify/add-source conflict resolvable' ' + git checkout B^0 && + + git merge -s recursive C^0 && + + test $(git rev-parse B:a) = $(git rev-parse b) && + test $(git rev-parse C:a) = $(git rev-parse a) +' + +test_expect_success 'setup resolvable conflict missed if rename missed' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m B && + + git checkout -b C A && + echo 6 >>a && + git add a && + git commit -m C +' + +test_expect_failure 'conflict caused if rename not detected' ' + git checkout -q C^0 && + git merge -s recursive B^0 && + + test 3 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test 6 -eq $(wc -l < c) && + test $(git rev-parse HEAD:a) = $(git rev-parse B:a) && + test $(git rev-parse HEAD:b) = $(git rev-parse A:b) +' + +test_expect_success 'setup conflict resolved wrong if rename missed' ' + git reset --hard && + git clean -f && + + git checkout -b D A && + echo 7 >>a && + git add a && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m D && + + git checkout -b E A && + git rm a && + echo "Completely different content" >>a && + git add a && + git commit -m E +' + +test_expect_failure 'missed conflict if rename not detected' ' + git checkout -q E^0 && + test_must_fail git merge -s recursive D^0 +' + +# Tests for undetected rename/add-source causing a file to erroneously be +# deleted (and for mishandled rename/rename(1to1) causing the same issue). +# +# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the +# same file is renamed on both sides to the same thing; it should trigger +# the 1to2 logic, which it would do if the add-source didn't cause issues +# for git's rename detection): +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->b, add unrelated a + +test_expect_success 'setup undetected rename/add-source causes data loss' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo foobar >a && + git add a && + git commit -m C +' + +test_expect_failure 'detect rename/add-source and preserve all data' ' + git checkout B^0 && + + git merge -s recursive C^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test -f a && + test -f b && + + test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && + test $(git rev-parse HEAD:a) = $(git rev-parse C:a) +' + +test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' + git checkout C^0 && + + git merge -s recursive B^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test -f a && + test -f b && + + test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && + test $(git rev-parse HEAD:a) = $(git rev-parse C:a) +' + +test_expect_success 'setup content merge + rename/directory conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n" >file && + git add file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>file && + mkdir newfile && + echo junk >newfile/realfile && + git add file newfile/realfile && + test_tick && + git commit -m right && + + git checkout -b left-conflict base && + echo 8 >>file && + git add file && + git mv file newfile && + test_tick && + git commit -m left && + + git checkout -b left-clean base && + echo 0 >newfile && + cat file >>newfile && + git add newfile && + git rm file && + test_tick && + git commit -m left +' + +test_expect_success 'rename/directory conflict + clean content merge' ' + git reset --hard && + git reset --hard && + git clean -fdqx && + + git checkout left-clean^0 && + + test_must_fail git merge -s recursive right^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 1 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + echo 0 >expect && + git cat-file -p base:file >>expect && + echo 7 >>expect && + test_cmp expect newfile~HEAD && + + test $(git rev-parse :2:newfile) = $(git hash-object expect) && + + test -f newfile/realfile && + test -f newfile~HEAD +' + +test_expect_success 'rename/directory conflict + content merge conflict' ' + git reset --hard && + git reset --hard && + git clean -fdqx && + + git checkout left-conflict^0 && + + test_must_fail git merge -s recursive right^0 && + + test 4 -eq $(git ls-files -s | wc -l) && + test 3 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + git cat-file -p left-conflict:newfile >left && + git cat-file -p base:file >base && + git cat-file -p right:file >right && + test_must_fail git merge-file \ + -L "HEAD:newfile" \ + -L "" \ + -L "right^0:file" \ + left base right && + test_cmp left newfile~HEAD && + + test $(git rev-parse :1:newfile) = $(git rev-parse base:file) && + test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) && + test $(git rev-parse :3:newfile) = $(git rev-parse right:file) && + + test -f newfile/realfile && + test -f newfile~HEAD +' + +test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' ' + git reset --hard && + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + mkdir sub && + printf "1\n2\n3\n4\n5\n6\n" >sub/file && + git add sub/file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>sub/file && + git add sub/file && + test_tick && + git commit -m right && + + git checkout -b left base && + echo 0 >newfile && + cat sub/file >>newfile && + git rm sub/file && + mv newfile sub && + git add sub && + test_tick && + git commit -m left +' + +test_expect_success 'disappearing dir in rename/directory conflict handled' ' + git reset --hard && + git clean -fdqx && + + git checkout left^0 && + + git merge -s recursive right^0 && + + test 1 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + echo 0 >expect && + git cat-file -p base:sub/file >>expect && + echo 7 >>expect && + test_cmp expect sub && + + test -f sub +' + +# Test for all kinds of things that can go wrong with rename/rename (2to1): +# Commit A: new files: a & b +# Commit B: rename a->c, modify b +# Commit C: rename b->c, modify a +# +# Merging of B & C should NOT be clean. Questions: +# * Both a & b should be removed by the merge; are they? +# * The two c's should contain modifications to a & b; do they? +# * The index should contain two files, both for c; does it? +# * The working copy should have two files, both of form c~<unique>; does it? +# * Nothing else should be present. Is anything? + +test_expect_success 'setup rename/rename (2to1) + modify/modify' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n" >a && + printf "5\n4\n3\n2\n1\n" >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo 0 >>b && + git add b && + git commit -m B && + + git checkout -b C A && + git mv b c && + echo 6 >>a && + git add a && + git commit -m C +' + +test_expect_success 'handle rename/rename (2to1) conflict correctly' ' + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 >out && + grep "CONFLICT (rename/rename)" out && + + test 2 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u | wc -l) && + test 2 -eq $(git ls-files -u c | wc -l) && + test 3 -eq $(git ls-files -o | wc -l) && + + test ! -f a && + test ! -f b && + test -f c~HEAD && + test -f c~C^0 && + + test $(git hash-object c~HEAD) = $(git rev-parse C:a) && + test $(git hash-object c~C^0) = $(git rev-parse B:b) +' + +# Testcase setup for simple rename/rename (1to2) conflict: +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c +test_expect_success 'setup simple rename/rename (1to2) conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + echo stuff >a && + git add a && + test_tick && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m B && + + git checkout -b C A && + git mv a c && + test_tick && + git commit -m C +' + +test_expect_success 'merge has correct working tree contents' ' + git checkout C^0 && + + test_must_fail git merge -s recursive B^0 && + + test 3 -eq $(git ls-files -s | wc -l) && + test 3 -eq $(git ls-files -u | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :1:a) = $(git rev-parse A:a) && + test $(git rev-parse :3:b) = $(git rev-parse A:a) && + test $(git rev-parse :2:c) = $(git rev-parse A:a) && + + test ! -f a && + test $(git hash-object b) = $(git rev-parse A:a) && + test $(git hash-object c) = $(git rev-parse A:a) +' + +# Testcase setup for rename/rename(1to2)/add-source conflict: +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c, add completely different a +# +# Merging of B & C should NOT be clean; there's a rename/rename conflict + +test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + echo something completely different >a && + git add a && + git commit -m C +' + +test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 && + + test 4 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse 3:a) = $(git rev-parse C:a) && + test $(git rev-parse 1:a) = $(git rev-parse A:a) && + test $(git rev-parse 2:b) = $(git rev-parse B:b) && + test $(git rev-parse 3:c) = $(git rev-parse C:c) && + + test -f a && + test -f b && + test -f c +' + +test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a b && + echo important-info >a && + git add a && + test_tick && + git commit -m two +' + +test_expect_failure 'rename/rename/add-source still tracks new a file' ' + git checkout C^0 && + git merge -s recursive B^0 && + + test 2 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse HEAD:a) = $(git rev-parse C:a) && + test $(git rev-parse HEAD:b) = $(git rev-parse A:a) +' + +test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + echo stuff >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + echo precious-data >c && + git add c && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a c && + echo important-info >b && + git add b && + test_tick && + git commit -m two +' + +test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' + git checkout C^0 && + test_must_fail git merge -s recursive B^0 && + + test 5 -eq $(git ls-files -s | wc -l) && + test 2 -eq $(git ls-files -u b | wc -l) && + test 2 -eq $(git ls-files -u c | wc -l) && + test 4 -eq $(git ls-files -o | wc -l) && + + test $(git rev-parse :1:a) = $(git rev-parse A:a) && + test $(git rev-parse :2:b) = $(git rev-parse C:b) && + test $(git rev-parse :3:b) = $(git rev-parse B:b) && + test $(git rev-parse :2:c) = $(git rev-parse C:c) && + test $(git rev-parse :3:c) = $(git rev-parse B:c) && + + test $(git hash-object c~HEAD) = $(git rev-parse C:c) && + test $(git hash-object c~B\^0) = $(git rev-parse B:c) && + test $(git hash-object b~HEAD) = $(git rev-parse C:b) && + test $(git hash-object b~B\^0) = $(git rev-parse B:b) && + + test ! -f b && + test ! -f c +' + +test_done diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index ed7575d0fd..320e1d1dbe 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -13,7 +13,7 @@ cleanup_fail() { test_expect_success 'setup' ' sane_unset GIT_PAGER GIT_PAGER_IN_USE && - test_might_fail git config --unset core.pager && + test_unconfig core.pager && PAGER="cat >paginated.out" && export PAGER && @@ -94,21 +94,19 @@ test_expect_success TTY 'no pager with --no-pager' ' test_expect_success TTY 'configuration can disable pager' ' rm -f paginated.out && - test_might_fail git config --unset pager.grep && + test_unconfig pager.grep && test_terminal git grep initial && test -e paginated.out && rm -f paginated.out && - git config pager.grep false && - test_when_finished "git config --unset pager.grep" && + test_config pager.grep false && test_terminal git grep initial && ! test -e paginated.out ' test_expect_success TTY 'git config uses a pager if configured to' ' rm -f paginated.out && - git config pager.config true && - test_when_finished "git config --unset pager.config" && + test_config pager.config true && test_terminal git config --list && test -e paginated.out ' @@ -116,8 +114,7 @@ test_expect_success TTY 'git config uses a pager if configured to' ' test_expect_success TTY 'configuration can enable pager (from subdir)' ' rm -f paginated.out && mkdir -p subdir && - git config pager.bundle true && - test_when_finished "git config --unset pager.bundle" && + test_config pager.bundle true && git bundle create test.bundle --all && rm -f paginated.out subdir/paginated.out && @@ -150,7 +147,7 @@ test_expect_success 'tests can detect color' ' test_expect_success 'no color when stdout is a regular file' ' rm -f colorless.log && - git config color.ui auto || + test_config color.ui auto || cleanup_fail && git log >colorless.log && @@ -159,7 +156,7 @@ test_expect_success 'no color when stdout is a regular file' ' test_expect_success TTY 'color when writing to a pager' ' rm -f paginated.out && - git config color.ui auto || + test_config color.ui auto || cleanup_fail && ( @@ -170,9 +167,21 @@ test_expect_success TTY 'color when writing to a pager' ' colorful paginated.out ' +test_expect_success TTY 'colors are suppressed by color.pager' ' + rm -f paginated.out && + test_config color.ui auto && + test_config color.pager false && + ( + TERM=vt100 && + export TERM && + test_terminal git log + ) && + ! colorful paginated.out +' + test_expect_success 'color when writing to a file intended for a pager' ' rm -f colorful.log && - git config color.ui auto || + test_config color.ui auto || cleanup_fail && ( @@ -184,6 +193,17 @@ test_expect_success 'color when writing to a file intended for a pager' ' colorful colorful.log ' +test_expect_success TTY 'colors are sent to pager for external commands' ' + test_config alias.externallog "!git log" && + test_config color.ui auto && + ( + TERM=vt100 && + export TERM && + test_terminal git -p externallog + ) && + colorful paginated.out +' + # Use this helper to make it easy for the caller of your # terminal-using function to specify whether it should fail. # If you write @@ -221,7 +241,7 @@ test_default_pager() { $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" " sane_unset PAGER GIT_PAGER && - test_might_fail git config --unset core.pager && + test_unconfig core.pager && rm -f default_pager_used || cleanup_fail && @@ -244,7 +264,7 @@ test_PAGER_overrides() { $test_expectation TTY "$cmd - PAGER overrides default pager" " sane_unset GIT_PAGER && - test_might_fail git config --unset core.pager && + test_unconfig core.pager && rm -f PAGER_used || cleanup_fail && @@ -277,7 +297,7 @@ test_core_pager() { PAGER=wc && export PAGER && - git config core.pager 'wc >core.pager_used' && + test_config core.pager 'wc >core.pager_used' && $full_command && ${if_local_config}test -e core.pager_used " @@ -307,7 +327,7 @@ test_pager_subdir_helper() { PAGER=wc && stampname=\$(pwd)/core.pager_used && export PAGER stampname && - git config core.pager 'wc >\"\$stampname\"' && + test_config core.pager 'wc >\"\$stampname\"' && mkdir sub && ( cd sub && @@ -324,7 +344,7 @@ test_GIT_PAGER_overrides() { rm -f GIT_PAGER_used || cleanup_fail && - git config core.pager wc && + test_config core.pager wc && GIT_PAGER='wc >GIT_PAGER_used' && export GIT_PAGER && $full_command && @@ -402,21 +422,21 @@ test_core_pager_subdir expect_success test_must_fail \ 'git -p apply </dev/null' test_expect_success TTY 'command-specific pager' ' - unset PAGER GIT_PAGER; + sane_unset PAGER GIT_PAGER && echo "foo:initial" >expect && >actual && - git config --unset core.pager && - git config pager.log "sed s/^/foo:/ >actual" && + test_unconfig core.pager && + test_config pager.log "sed s/^/foo:/ >actual" && test_terminal git log --format=%s -1 && test_cmp expect actual ' test_expect_success TTY 'command-specific pager overrides core.pager' ' - unset PAGER GIT_PAGER; + sane_unset PAGER GIT_PAGER && echo "foo:initial" >expect && >actual && - git config core.pager "exit 1" - git config pager.log "sed s/^/foo:/ >actual" && + test_config core.pager "exit 1" + test_config pager.log "sed s/^/foo:/ >actual" && test_terminal git log --format=%s -1 && test_cmp expect actual ' @@ -425,9 +445,45 @@ test_expect_success TTY 'command-specific pager overridden by environment' ' GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER && >actual && echo "foo:initial" >expect && - git config pager.log "exit 1" && + test_config pager.log "exit 1" && test_terminal git log --format=%s -1 && test_cmp expect actual ' +test_expect_success 'setup external command' ' + cat >git-external <<-\EOF && + #!/bin/sh + git "$@" + EOF + chmod +x git-external +' + +test_expect_success TTY 'command-specific pager works for external commands' ' + sane_unset PAGER GIT_PAGER && + echo "foo:initial" >expect && + >actual && + test_config pager.external "sed s/^/foo:/ >actual" && + test_terminal git --exec-path="`pwd`" external log --format=%s -1 && + test_cmp expect actual +' + +test_expect_success TTY 'sub-commands of externals use their own pager' ' + sane_unset PAGER GIT_PAGER && + echo "foo:initial" >expect && + >actual && + test_config pager.log "sed s/^/foo:/ >actual" && + test_terminal git --exec-path=. external log --format=%s -1 && + test_cmp expect actual +' + +test_expect_success TTY 'external command pagers override sub-commands' ' + sane_unset PAGER GIT_PAGER && + >expect && + >actual && + test_config pager.external false && + test_config pager.log "sed s/^/log:/ >actual" && + test_terminal git --exec-path=. external log --format=%s -1 && + test_cmp expect actual +' + test_done diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh index e058d184d1..917a264eea 100755 --- a/t/t7008-grep-binary.sh +++ b/t/t7008-grep-binary.sh @@ -84,7 +84,7 @@ test_expect_success 'git grep -Fi Y<NUL>f a' " git grep -f f -Fi a " -test_expect_failure 'git grep -Fi Y<NUL>x a' " +test_expect_success 'git grep -Fi Y<NUL>x a' " printf 'YQx' | q_to_nul >f && test_must_fail git grep -f f -Fi a " @@ -94,7 +94,7 @@ test_expect_success 'git grep y<NUL>f a' " git grep -f f a " -test_expect_failure 'git grep y<NUL>x a' " +test_expect_success 'git grep y<NUL>x a' " printf 'yQx' | q_to_nul >f && test_must_fail git grep -f f a " diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index 0a46795ae7..61f36baa1f 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -53,7 +53,7 @@ cat >expected <<\EOF Trying simple merge with c2 Trying simple merge with c3 Trying simple merge with c4 -Merge made by octopus. +Merge made by the 'octopus' strategy. c2.c | 1 + c3.c | 1 + c4.c | 1 + @@ -72,7 +72,7 @@ test_expect_success 'merge output uses pretty names' ' cat >expected <<\EOF Already up-to-date with c4 Trying simple merge with c5 -Merge made by octopus. +Merge made by the 'octopus' strategy. c5.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 c5.c @@ -86,7 +86,7 @@ test_expect_success 'merge up-to-date output uses pretty names' ' cat >expected <<\EOF Fast-forwarding to: c1 Trying simple merge with c2 -Merge made by octopus. +Merge made by the 'octopus' strategy. c1.c | 1 + c2.c | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh index 3ab43902b3..8c9539e1b4 100755 --- a/t/t9158-git-svn-mergeinfo.sh +++ b/t/t9158-git-svn-mergeinfo.sh @@ -38,4 +38,17 @@ test_expect_success 'verify svn:mergeinfo' ' test "$mergeinfo" = "/branches/foo:1-10" ' +test_expect_success 'change svn:mergeinfo multiline' ' + touch baz && + git add baz && + git commit -m "baz" && + git svn dcommit --mergeinfo="/branches/bar:1-10 /branches/other:3-5,8,10-11" +' + +test_expect_success 'verify svn:mergeinfo multiline' ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk) + test "$mergeinfo" = "/branches/bar:1-10 +/branches/other:3-5,8,10-11" +' + test_done diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh new file mode 100755 index 0000000000..b4a4434604 --- /dev/null +++ b/t/t9160-git-svn-preserve-empty-dirs.sh @@ -0,0 +1,153 @@ +#!/bin/sh +# +# Copyright (c) 2011 Ray Chen +# + +test_description='git svn test (option --preserve-empty-dirs) + +This test uses git to clone a Subversion repository that contains empty +directories, and checks that corresponding directories are created in the +local Git repository with placeholder files.' + +. ./lib-git-svn.sh + +say 'define NO_SVN_TESTS to skip git svn tests' +GIT_REPO=git-svn-repo + +test_expect_success 'initialize source svn repo containing empty dirs' ' + svn_cmd mkdir -m x "$svnrepo"/trunk && + svn_cmd co "$svnrepo"/trunk "$SVN_TREE" && + ( + cd "$SVN_TREE" && + mkdir -p 1 2 3/a 3/b 4 5 6 && + echo "First non-empty file" > 2/file1.txt && + echo "Second non-empty file" > 2/file2.txt && + echo "Third non-empty file" > 3/a/file1.txt && + echo "Fourth non-empty file" > 3/b/file1.txt && + svn_cmd add 1 2 3 4 5 6 && + svn_cmd commit -m "initial commit" && + + mkdir 4/a && + svn_cmd add 4/a && + svn_cmd commit -m "nested empty directory" && + mkdir 4/a/b && + svn_cmd add 4/a/b && + svn_cmd commit -m "deeply nested empty directory" && + mkdir 4/a/b/c && + svn_cmd add 4/a/b/c && + svn_cmd commit -m "really deeply nested empty directory" && + echo "Kill the placeholder file" > 4/a/b/c/foo && + svn_cmd add 4/a/b/c/foo && + svn_cmd commit -m "Regular file to remove placeholder" && + + svn_cmd del 2/file2.txt && + svn_cmd del 3/b && + svn_cmd commit -m "delete non-last entry in directory" && + + svn_cmd del 2/file1.txt && + svn_cmd del 3/a && + svn_cmd commit -m "delete last entry in directory" && + + echo "Conflict file" > 5/.placeholder && + mkdir 6/.placeholder && + svn_cmd add 5/.placeholder 6/.placeholder && + svn_cmd commit -m "Placeholder Namespace conflict" + ) && + rm -rf "$SVN_TREE" +' + +test_expect_success 'clone svn repo with --preserve-empty-dirs' ' + git svn clone "$svnrepo"/trunk --preserve-empty-dirs "$GIT_REPO" +' + +# "$GIT_REPO"/1 should only contain the placeholder file. +test_expect_success 'directory empty from inception' ' + test -f "$GIT_REPO"/1/.gitignore && + test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" +' + +# "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file. +test_expect_success 'directory empty from subsequent svn commit' ' + test -f "$GIT_REPO"/2/.gitignore && + test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" && + test -f "$GIT_REPO"/3/.gitignore && + test $(find "$GIT_REPO"/3 -type f | wc -l) = "1" +' + +# No placeholder files should exist in "$GIT_REPO"/4, even though one was +# generated for every sub-directory at some point in the repo's history. +test_expect_success 'add entry to previously empty directory' ' + test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" && + test -f "$GIT_REPO"/4/a/b/c/foo +' + +# The HEAD~2 commit should not have introduced .gitignore placeholder files. +test_expect_success 'remove non-last entry from directory' ' + ( + cd "$GIT_REPO" && + git checkout HEAD~2 + ) && + test_must_fail test -f "$GIT_REPO"/2/.gitignore && + test_must_fail test -f "$GIT_REPO"/3/.gitignore +' + +# After re-cloning the repository with --placeholder-file specified, there +# should be 5 files named ".placeholder" in the local Git repo. +test_expect_success 'clone svn repo with --placeholder-file specified' ' + rm -rf "$GIT_REPO" && + git svn clone "$svnrepo"/trunk --preserve-empty-dirs \ + --placeholder-file=.placeholder "$GIT_REPO" && + find "$GIT_REPO" -type f -name ".placeholder" && + test $(find "$GIT_REPO" -type f -name ".placeholder" | wc -l) = "5" +' + +# "$GIT_REPO"/5/.placeholder should be a file, and non-empty. +test_expect_success 'placeholder namespace conflict with file' ' + test -s "$GIT_REPO"/5/.placeholder +' + +# "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree +# should only contain one file: the placeholder. +test_expect_success 'placeholder namespace conflict with directory' ' + test -d "$GIT_REPO"/6/.placeholder && + test -f "$GIT_REPO"/6/.placeholder/.placeholder && + test $(find "$GIT_REPO"/6 -type f | wc -l) = "1" +' + +# Prepare a second set of svn commits to test persistence during rebase. +test_expect_success 'second set of svn commits and rebase' ' + svn_cmd co "$svnrepo"/trunk "$SVN_TREE" && + ( + cd "$SVN_TREE" && + mkdir -p 7 && + echo "This should remove placeholder" > 1/file1.txt && + echo "This should not remove placeholder" > 5/file1.txt && + svn_cmd add 7 1/file1.txt 5/file1.txt && + svn_cmd commit -m "subsequent svn commit for persistence tests" + ) && + rm -rf "$SVN_TREE" && + ( + cd "$GIT_REPO" && + git svn rebase + ) +' + +# Check that --preserve-empty-dirs and --placeholder-file flag state +# stays persistent over multiple invocations. +test_expect_success 'flag persistence during subsqeuent rebase' ' + test -f "$GIT_REPO"/7/.placeholder && + test $(find "$GIT_REPO"/7 -type f | wc -l) = "1" +' + +# Check that placeholder files are properly removed when unnecessary, +# even across multiple invocations. +test_expect_success 'placeholder list persistence during subsqeuent rebase' ' + test -f "$GIT_REPO"/1/file1.txt && + test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" && + + test -f "$GIT_REPO"/5/file1.txt && + test -f "$GIT_REPO"/5/.placeholder && + test $(find "$GIT_REPO"/5 -type f | wc -l) = "2" +' + +test_done diff --git a/t/t9161-git-svn-mergeinfo-push.sh b/t/t9161-git-svn-mergeinfo-push.sh new file mode 100755 index 0000000000..6ef0c0bde3 --- /dev/null +++ b/t/t9161-git-svn-mergeinfo-push.sh @@ -0,0 +1,104 @@ +#!/bin/sh +# +# Portions copyright (c) 2007, 2009 Sam Vilain +# Portions copyright (c) 2011 Bryan Jacobs +# + +test_description='git-svn svn mergeinfo propagation' + +. ./lib-git-svn.sh + +test_expect_success 'load svn dump' " + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9161/branches.dump' && + git svn init --minimize-url -R svnmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +test_expect_success 'propagate merge information' ' + git config svn.pushmergeinfo yes && + git checkout svnb1 && + git merge --no-ff svnb2 && + git svn dcommit + ' + +test_expect_success 'check svn:mergeinfo' ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1) + test "$mergeinfo" = "/branches/svnb2:3,8" + ' + +test_expect_success 'merge another branch' ' + git merge --no-ff svnb3 && + git svn dcommit + ' + +test_expect_success 'check primary parent mergeinfo respected' ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1) + test "$mergeinfo" = "/branches/svnb2:3,8 +/branches/svnb3:4,9" + ' + +test_expect_success 'merge existing merge' ' + git merge --no-ff svnb4 && + git svn dcommit + ' + +test_expect_success "check both parents' mergeinfo respected" ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1) + test "$mergeinfo" = "/branches/svnb2:3,8 +/branches/svnb3:4,9 +/branches/svnb4:5-6,10-12 +/branches/svnb5:6,11" + ' + +test_expect_success 'make further commits to branch' ' + git checkout svnb2 && + touch newb2file && + git add newb2file && + git commit -m "later b2 commit" && + touch newb2file-2 && + git add newb2file-2 && + git commit -m "later b2 commit 2" && + git svn dcommit + ' + +test_expect_success 'second forward merge' ' + git checkout svnb1 && + git merge --no-ff svnb2 && + git svn dcommit + ' + +test_expect_success 'check new mergeinfo added' ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1) + test "$mergeinfo" = "/branches/svnb2:3,8,16-17 +/branches/svnb3:4,9 +/branches/svnb4:5-6,10-12 +/branches/svnb5:6,11" + ' + +test_expect_success 'reintegration merge' ' + git checkout svnb4 && + git merge --no-ff svnb1 && + git svn dcommit + ' + +test_expect_success 'check reintegration mergeinfo' ' + mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb4) + test "$mergeinfo" = "/branches/svnb1:2-4,7-9,13-18 +/branches/svnb2:3,8,16-17 +/branches/svnb3:4,9 +/branches/svnb4:5-6,10-12 +/branches/svnb5:6,11" + ' + +test_expect_success 'dcommit a merge at the top of a stack' ' + git checkout svnb1 && + touch anotherfile && + git add anotherfile && + git commit -m "a commit" && + git merge svnb4 && + git svn dcommit + ' + +test_done diff --git a/t/t9161/branches.dump b/t/t9161/branches.dump new file mode 100644 index 0000000000..e61c3e7236 --- /dev/null +++ b/t/t9161/branches.dump @@ -0,0 +1,374 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1ef08553-f2d1-45df-b38c-19af6b7c926d + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2011-09-02T16:08:02.941384Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 12 +Base commit + +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:08:27.205062Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 19 +Create branch svnb1 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:09:43.628137Z +PROPS-END + +Node-path: branches/svnb1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 3 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 19 +Create branch svnb2 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:09:46.339930Z +PROPS-END + +Node-path: branches/svnb2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 19 +Create branch svnb3 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:09:49.394515Z +PROPS-END + +Node-path: branches/svnb3 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 5 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 19 +Create branch svnb4 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:09:54.114607Z +PROPS-END + +Node-path: branches/svnb4 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 6 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 19 +Create branch svnb5 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:09:58.602623Z +PROPS-END + +Node-path: branches/svnb5 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 7 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +b1 commit +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:10:20.292369Z +PROPS-END + +Node-path: branches/svnb1/b1file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 8 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +b2 commit +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:10:38.429199Z +PROPS-END + +Node-path: branches/svnb2/b2file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 9 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +b3 commit +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:10:52.843023Z +PROPS-END + +Node-path: branches/svnb3/b3file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 10 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +b4 commit +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:11:17.489870Z +PROPS-END + +Node-path: branches/svnb4/b4file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 11 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +b5 commit +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:11:32.277404Z +PROPS-END + +Node-path: branches/svnb5/b5file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 12 +Prop-content-length: 192 +Content-length: 192 + +K 7 +svn:log +V 90 +Merge remote-tracking branch 'svnb5' into HEAD + +* svnb5: + b5 commit + Create branch svnb5 +K 10 +svn:author +V 7 +bjacobs +K 8 +svn:date +V 27 +2011-09-02T16:11:54.274722Z +PROPS-END + +Node-path: branches/svnb4 +Node-kind: dir +Node-action: change +Prop-content-length: 56 +Content-length: 56 + +K 13 +svn:mergeinfo +V 21 +/branches/svnb5:6,11 + +PROPS-END + + +Node-path: branches/svnb4/b5file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 2cb4494262..1a6c06631c 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -94,6 +94,12 @@ data <<EOF An annotated tag without a tagger EOF +tag series-A-blob +from :3 +data <<EOF +An annotated tag that annotates a blob. +EOF + INPUT_END test_expect_success \ 'A: create pack from stdin' \ @@ -152,6 +158,18 @@ test_expect_success 'A: verify tag/series-A' ' ' cat >expect <<EOF +object $(git rev-parse refs/heads/master:file3) +type blob +tag series-A-blob + +An annotated tag that annotates a blob. +EOF +test_expect_success 'A: verify tag/series-A-blob' ' + git cat-file tag tags/series-A-blob >actual && + test_cmp expect actual +' + +cat >expect <<EOF :2 `git rev-parse --verify master:file2` :3 `git rev-parse --verify master:file3` :4 `git rev-parse --verify master:file4` @@ -170,6 +188,55 @@ test_expect_success \ test_cmp expect marks.new' test_tick +new_blob=$(echo testing | git hash-object --stdin) +cat >input <<INPUT_END +tag series-A-blob-2 +from $(git rev-parse refs/heads/master:file3) +data <<EOF +Tag blob by sha1. +EOF + +blob +mark :6 +data <<EOF +testing +EOF + +commit refs/heads/new_blob +committer <> 0 +0000 +data 0 +M 644 :6 new_blob +#pretend we got sha1 from fast-import +ls "new_blob" + +tag series-A-blob-3 +from $new_blob +data <<EOF +Tag new_blob. +EOF +INPUT_END + +cat >expect <<EOF +object $(git rev-parse refs/heads/master:file3) +type blob +tag series-A-blob-2 + +Tag blob by sha1. +object $new_blob +type blob +tag series-A-blob-3 + +Tag new_blob. +EOF + +test_expect_success \ + 'A: tag blob by sha1' \ + 'git fast-import <input && + git cat-file tag tags/series-A-blob-2 >actual && + git cat-file tag tags/series-A-blob-3 >>actual && + test_cmp expect actual' + +test_tick cat >input <<INPUT_END commit refs/heads/verify--import-marks committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE @@ -324,6 +391,105 @@ test_expect_success \ test `git rev-parse master` = `git rev-parse TEMP_TAG^`' rm -f .git/TEMP_TAG +git gc 2>/dev/null >/dev/null +git prune 2>/dev/null >/dev/null + +cat >input <<INPUT_END +commit refs/heads/empty-committer-1 +committer <> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: accept empty committer' ' + git fast-import <input && + out=$(git fsck) && + echo "$out" && + test -z "$out" +' +git update-ref -d refs/heads/empty-committer-1 || true + +git gc 2>/dev/null >/dev/null +git prune 2>/dev/null >/dev/null + +cat >input <<INPUT_END +commit refs/heads/empty-committer-2 +committer <a@b.com> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: accept and fixup committer with no name' ' + git fast-import <input && + out=$(git fsck) && + echo "$out" && + test -z "$out" +' +git update-ref -d refs/heads/empty-committer-2 || true + +git gc 2>/dev/null >/dev/null +git prune 2>/dev/null >/dev/null + +cat >input <<INPUT_END +commit refs/heads/invalid-committer +committer Name email> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: fail on invalid committer (1)' ' + test_must_fail git fast-import <input +' +git update-ref -d refs/heads/invalid-committer || true + +cat >input <<INPUT_END +commit refs/heads/invalid-committer +committer Name <e<mail> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: fail on invalid committer (2)' ' + test_must_fail git fast-import <input +' +git update-ref -d refs/heads/invalid-committer || true + +cat >input <<INPUT_END +commit refs/heads/invalid-committer +committer Name <email>> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: fail on invalid committer (3)' ' + test_must_fail git fast-import <input +' +git update-ref -d refs/heads/invalid-committer || true + +cat >input <<INPUT_END +commit refs/heads/invalid-committer +committer Name <email $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: fail on invalid committer (4)' ' + test_must_fail git fast-import <input +' +git update-ref -d refs/heads/invalid-committer || true + +cat >input <<INPUT_END +commit refs/heads/invalid-committer +committer Name<email> $GIT_COMMITTER_DATE +data <<COMMIT +empty commit +COMMIT +INPUT_END +test_expect_success 'B: fail on invalid committer (5)' ' + test_must_fail git fast-import <input +' +git update-ref -d refs/heads/invalid-committer || true + ### ### series C ### @@ -734,6 +900,47 @@ test_expect_success \ git diff-tree --abbrev --raw L^ L >output && test_cmp expect output' +cat >input <<INPUT_END +blob +mark :1 +data <<EOF +the data +EOF + +commit refs/heads/L2 +committer C O Mitter <committer@example.com> 1112912473 -0700 +data <<COMMIT +init L2 +COMMIT +M 644 :1 a/b/c +M 644 :1 a/b/d +M 644 :1 a/e/f + +commit refs/heads/L2 +committer C O Mitter <committer@example.com> 1112912473 -0700 +data <<COMMIT +update L2 +COMMIT +C a g +C a/e g/b +M 644 :1 g/b/h +INPUT_END + +cat <<EOF >expect +g/b/f +g/b/h +EOF + +test_expect_success \ + 'L: nested tree copy does not corrupt deltas' \ + 'git fast-import <input && + git ls-tree L2 g/b/ >tmp && + cat tmp | cut -f 2 >actual && + test_cmp expect actual && + git fsck `git rev-parse L2`' + +git update-ref -d refs/heads/L2 + ### ### series M ### diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh index 97ec9753b4..01ba041fde 100755 --- a/t/t9800-git-p4.sh +++ b/t/t9800-git-p4.sh @@ -269,6 +269,203 @@ test_expect_success 'initial import time from top change time' ' test $p4time = $gittime ' +# Rename a file and confirm that rename is not detected in P4. +# Rename the new file again with detectRenames option enabled and confirm that +# this is detected in P4. +# Rename the new file again adding an extra line, configure a big threshold in +# detectRenames and confirm that rename is not detected in P4. +# Repeat, this time with a smaller threshold and confirm that the rename is +# detected in P4. +test_expect_success 'detect renames' ' + "$GITP4" clone --dest="$git" //depot@all && + test_when_finished cleanup_git && + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + + git mv file1 file4 && + git commit -a -m "Rename file1 to file4" && + git diff-tree -r -M HEAD && + "$GITP4" submit && + p4 filelog //depot/file4 && + ! p4 filelog //depot/file4 | grep -q "branch from" && + + git mv file4 file5 && + git commit -a -m "Rename file4 to file5" && + git diff-tree -r -M HEAD && + git config git-p4.detectRenames true && + "$GITP4" submit && + p4 filelog //depot/file5 && + p4 filelog //depot/file5 | grep -q "branch from //depot/file4" && + + git mv file5 file6 && + echo update >>file6 && + git add file6 && + git commit -a -m "Rename file5 to file6 with changes" && + git diff-tree -r -M HEAD && + level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && + test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && + git config git-p4.detectRenames $((level + 2)) && + "$GITP4" submit && + p4 filelog //depot/file6 && + ! p4 filelog //depot/file6 | grep -q "branch from" && + + git mv file6 file7 && + echo update >>file7 && + git add file7 && + git commit -a -m "Rename file6 to file7 with changes" && + git diff-tree -r -M HEAD && + level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && + test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && + git config git-p4.detectRenames $((level - 2)) && + "$GITP4" submit && + p4 filelog //depot/file7 && + p4 filelog //depot/file7 | grep -q "branch from //depot/file6" +' + +# Copy a file and confirm that copy is not detected in P4. +# Copy a file with detectCopies option enabled and confirm that copy is not +# detected in P4. +# Modify and copy a file with detectCopies option enabled and confirm that copy +# is detected in P4. +# Copy a file with detectCopies and detectCopiesHarder options enabled and +# confirm that copy is detected in P4. +# Modify and copy a file, configure a bigger threshold in detectCopies and +# confirm that copy is not detected in P4. +# Modify and copy a file, configure a smaller threshold in detectCopies and +# confirm that copy is detected in P4. +test_expect_success 'detect copies' ' + "$GITP4" clone --dest="$git" //depot@all && + test_when_finished cleanup_git && + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + + cp file2 file8 && + git add file8 && + git commit -a -m "Copy file2 to file8" && + git diff-tree -r -C HEAD && + "$GITP4" submit && + p4 filelog //depot/file8 && + ! p4 filelog //depot/file8 | grep -q "branch from" && + + cp file2 file9 && + git add file9 && + git commit -a -m "Copy file2 to file9" && + git diff-tree -r -C HEAD && + git config git-p4.detectCopies true && + "$GITP4" submit && + p4 filelog //depot/file9 && + ! p4 filelog //depot/file9 | grep -q "branch from" && + + echo "file2" >>file2 && + cp file2 file10 && + git add file2 file10 && + git commit -a -m "Modify and copy file2 to file10" && + git diff-tree -r -C HEAD && + "$GITP4" submit && + p4 filelog //depot/file10 && + p4 filelog //depot/file10 | grep -q "branch from //depot/file" && + + cp file2 file11 && + git add file11 && + git commit -a -m "Copy file2 to file11" && + git diff-tree -r -C --find-copies-harder HEAD && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopiesHarder true && + "$GITP4" submit && + p4 filelog //depot/file11 && + p4 filelog //depot/file11 | grep -q "branch from //depot/file" && + + cp file2 file12 && + echo "some text" >>file12 && + git add file12 && + git commit -a -m "Copy file2 to file12 with changes" && + git diff-tree -r -C --find-copies-harder HEAD && + level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && + test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopies $((level + 2)) && + "$GITP4" submit && + p4 filelog //depot/file12 && + ! p4 filelog //depot/file12 | grep -q "branch from" && + + cp file2 file13 && + echo "different text" >>file13 && + git add file13 && + git commit -a -m "Copy file2 to file13 with changes" && + git diff-tree -r -C --find-copies-harder HEAD && + level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && + test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopies $((level - 2)) && + "$GITP4" submit && + p4 filelog //depot/file13 && + p4 filelog //depot/file13 | grep -q "branch from //depot/file" +' + +# Create a simple branch structure in P4 depot to check if it is correctly +# cloned. +test_expect_success 'add simple p4 branches' ' + cd "$cli" && + mkdir branch1 && + cd branch1 && + echo file1 >file1 && + echo file2 >file2 && + p4 add file1 file2 && + p4 submit -d "branch1" && + p4 integrate //depot/branch1/... //depot/branch2/... && + p4 submit -d "branch2" && + echo file3 >file3 && + p4 add file3 && + p4 submit -d "add file3 in branch1" && + p4 open file2 && + echo update >>file2 && + p4 submit -d "update file2 in branch1" && + p4 integrate //depot/branch1/... //depot/branch3/... && + p4 submit -d "branch3" && + cd "$TRASH_DIRECTORY" +' + +# Configure branches through git-config and clone them. +# All files are tested to make sure branches were cloned correctly. +# Finally, make an update to branch1 on P4 side to check if it is imported +# correctly by git-p4. +test_expect_success 'git-p4 clone simple branches' ' + test_when_finished cleanup_git && + test_create_repo "$git" && + cd "$git" && + git config git-p4.branchList branch1:branch2 && + git config --add git-p4.branchList branch1:branch3 && + "$GITP4" clone --dest=. --detect-branches //depot@all && + git log --all --graph --decorate --stat && + git reset --hard p4/depot/branch1 && + test -f file1 && + test -f file2 && + test -f file3 && + grep -q update file2 && + git reset --hard p4/depot/branch2 && + test -f file1 && + test -f file2 && + test ! -f file3 && + ! grep -q update file2 && + git reset --hard p4/depot/branch3 && + test -f file1 && + test -f file2 && + test -f file3 && + grep -q update file2 && + cd "$cli" && + cd branch1 && + p4 edit file2 && + echo file2_ >>file2 && + p4 submit -d "update file2 in branch1" && + cd "$git" && + git reset --hard p4/depot/branch1 && + "$GITP4" rebase && + grep -q file2_ file2 +' + test_expect_success 'shutdown' ' pid=`pgrep -f p4d` && test -n "$pid" && diff --git a/t/test-lib.sh b/t/test-lib.sh index c210c17c29..bdd9513b84 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -361,6 +361,24 @@ test_chmod () { git update-index --add "--chmod=$@" } +# Unset a configuration variable, but don't fail if it doesn't exist. +test_unconfig () { + git config --unset-all "$@" + config_status=$? + case "$config_status" in + 5) # ok, nothing to unset + config_status=0 + ;; + esac + return $config_status +} + +# Set git config, automatically unsetting it after the test is over. +test_config () { + test_when_finished "test_unconfig '$1'" && + git config "$@" +} + # Use test_set_prereq to tell that a particular prerequisite is available. # The prerequisite can later be checked for in two ways: # diff --git a/templates/hooks--post-commit.sample b/templates/hooks--post-commit.sample deleted file mode 100755 index 22668216a3..0000000000 --- a/templates/hooks--post-commit.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script that is called after a successful -# commit is made. -# -# To enable this hook, rename this file to "post-commit". - -: Nothing diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample deleted file mode 100755 index 7a83e17ab5..0000000000 --- a/templates/hooks--post-receive.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script for the "post-receive" event. -# -# The "post-receive" script is run after receive-pack has accepted a pack -# and the repository has been updated. It is passed arguments in through -# stdin in the form -# <oldrev> <newrev> <refname> -# For example: -# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master -# -# see contrib/hooks/ for a sample, or uncomment the next line and -# rename the file to "post-receive". - -#. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/transport.c b/transport.c index 98c577804f..fa279d531f 100644 --- a/transport.c +++ b/transport.c @@ -10,6 +10,7 @@ #include "refs.h" #include "branch.h" #include "url.h" +#include "submodule.h" /* rsync support */ @@ -482,18 +483,14 @@ static int set_git_option(struct git_transport_options *opts, static int connect_setup(struct transport *transport, int for_push, int verbose) { struct git_transport_data *data = transport->data; - struct strbuf sb = STRBUF_INIT; if (data->conn) return 0; - strbuf_addstr(&sb, for_push ? data->options.receivepack : - data->options.uploadpack); - if (for_push && transport->verbose < 0) - strbuf_addstr(&sb, " --quiet"); - data->conn = git_connect(data->fd, transport->url, sb.buf, + data->conn = git_connect(data->fd, transport->url, + for_push ? data->options.receivepack : + data->options.uploadpack, verbose ? CONNECT_VERBOSE : 0); - strbuf_release(&sb); return 0; } @@ -1045,6 +1042,14 @@ int transport_push(struct transport *transport, flags & TRANSPORT_PUSH_MIRROR, flags & TRANSPORT_PUSH_FORCE); + if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) { + struct ref *ref = remote_refs; + for (; ref; ref = ref->next) + if (!is_null_sha1(ref->new_sha1) && + check_submodule_needs_pushing(ref->new_sha1,transport->remote->name)) + die("There are unpushed submodules, aborting."); + } + push_ret = transport->push_refs(transport, remote_refs, flags); err = push_had_errors(remote_refs); ret = push_ret | err; diff --git a/transport.h b/transport.h index 161d724bba..059b3303e2 100644 --- a/transport.h +++ b/transport.h @@ -101,6 +101,7 @@ struct transport { #define TRANSPORT_PUSH_MIRROR 8 #define TRANSPORT_PUSH_PORCELAIN 16 #define TRANSPORT_PUSH_SET_UPSTREAM 32 +#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) diff --git a/tree-walk.c b/tree-walk.c index 33f749e1e7..808bb55ba3 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t) } } +static inline int prune_traversal(struct name_entry *e, + struct traverse_info *info, + struct strbuf *base, + int still_interesting) +{ + if (!info->pathspec || still_interesting == 2) + return 2; + if (still_interesting < 0) + return still_interesting; + return tree_entry_interesting(e, base, 0, info->pathspec); +} + int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { int ret = 0; @@ -316,10 +328,18 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) struct name_entry *entry = xmalloc(n*sizeof(*entry)); int i; struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); + struct strbuf base = STRBUF_INIT; + int interesting = 1; for (i = 0; i < n; i++) tx[i].d = t[i]; + if (info->prev) { + strbuf_grow(&base, info->pathlen); + make_traverse_path(base.buf, info->prev, &info->name); + base.buf[info->pathlen-1] = '/'; + strbuf_setlen(&base, info->pathlen); + } for (;;) { unsigned long mask, dirmask; const char *first = NULL; @@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) mask |= 1ul << i; if (S_ISDIR(entry[i].mode)) dirmask |= 1ul << i; + e = &entry[i]; } if (!mask) break; - ret = info->fn(n, mask, dirmask, entry, info); - if (ret < 0) { - error = ret; - if (!info->show_all_errors) - break; + interesting = prune_traversal(e, info, &base, interesting); + if (interesting < 0) + break; + if (interesting) { + ret = info->fn(n, mask, dirmask, entry, info); + if (ret < 0) { + error = ret; + if (!info->show_all_errors) + break; + } + mask &= ret; } - mask &= ret; ret = 0; for (i = 0; i < n; i++) if (mask & (1ul << i)) @@ -395,6 +421,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); + strbuf_release(&base); return error; } diff --git a/tree-walk.h b/tree-walk.h index 39524b7dba..0089581e1d 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -44,6 +44,7 @@ struct traverse_info { struct traverse_info *prev; struct name_entry name; int pathlen; + struct pathspec *pathspec; unsigned long conflicts; traverse_callback_t fn; diff --git a/unpack-trees.c b/unpack-trees.c index cc616c3f99..670b464738 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -444,6 +444,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo = *info; newinfo.prev = info; + newinfo.pathspec = info->pathspec; newinfo.name = *p; newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1; newinfo.conflicts |= df_conflicts; @@ -1040,6 +1041,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; info.show_all_errors = o->show_all_errors; + info.pathspec = o->pathspec; if (o->prefix) { /* diff --git a/unpack-trees.h b/unpack-trees.h index 7998948307..5e432f576e 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -52,6 +52,7 @@ struct unpack_trees_options { const char *prefix; int cache_bottom; struct dir_struct *dir; + struct pathspec *pathspec; merge_fn_t fn; const char *msgs[NB_UNPACK_TREES_ERROR_TYPES]; /* diff --git a/wt-status.c b/wt-status.c index 02377729c4..8836a527d0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -26,7 +26,9 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = { static const char *color(int slot, struct wt_status *s) { - const char *c = s->use_color > 0 ? s->color_palette[slot] : ""; + const char *c = ""; + if (want_color(s->use_color)) + c = s->color_palette[slot]; if (slot == WT_STATUS_ONBRANCH && color_is_nil(c)) c = s->color_palette[WT_STATUS_HEADER]; return c; @@ -681,7 +683,7 @@ static void wt_status_print_verbose(struct wt_status *s) * will have checked isatty on stdout). */ if (s->fp != stdout) - DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF); + rev.diffopt.use_color = 0; run_diff_index(&rev, 1); } diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index 620fc9a657..5a33d1a869 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c @@ -36,6 +36,7 @@ typedef struct s_xdlclass { char const *line; long size; long idx; + long len1, len2; } xdlclass_t; typedef struct s_xdlclassifier { @@ -43,6 +44,8 @@ typedef struct s_xdlclassifier { long hsize; xdlclass_t **rchash; chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; long count; long flags; } xdlclassifier_t; @@ -52,15 +55,15 @@ typedef struct s_xdlclassifier { static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); static void xdl_free_classifier(xdlclassifier_t *cf); -static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits, - xrecord_t *rec); -static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp, +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, xdlclassifier_t *cf, xdfile_t *xdf); static void xdl_free_ctx(xdfile_t *xdf); static int xdl_clean_mmatch(char const *dis, long i, long s, long e); -static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); -static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); @@ -82,6 +85,14 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { } memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + cf->count = 0; return 0; @@ -90,16 +101,18 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { static void xdl_free_classifier(xdlclassifier_t *cf) { + xdl_free(cf->rcrecs); xdl_free(cf->rchash); xdl_cha_free(&cf->ncha); } -static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits, - xrecord_t *rec) { +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { long hi; char const *line; xdlclass_t *rcrec; + xdlclass_t **rcrecs; line = rec->ptr; hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); @@ -115,13 +128,25 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned return -1; } rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; rcrec->line = line; rcrec->size = rec->size; rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; rcrec->next = cf->rchash[hi]; cf->rchash[hi] = rcrec; } + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + rec->ha = (unsigned long) rcrec->idx; hi = (long) XDL_HASHLONG(rec->ha, hbits); @@ -132,7 +157,7 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned } -static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp, +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, xdlclassifier_t *cf, xdfile_t *xdf) { unsigned int hbits; long nrec, hsize, bsize; @@ -185,7 +210,7 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp, recs[nrec++] = crec; if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && - xdl_classify_record(cf, rhash, hbits, crec) < 0) + xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) goto abort; } } @@ -239,6 +264,8 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, long enl1, enl2, sample; xdlclassifier_t cf; + memset(&cf, 0, sizeof(cf)); + /* * For histogram diff, we can afford a smaller sample size and * thus a poorer estimate of the number of lines, as the hash @@ -257,30 +284,30 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, return -1; } - if (xdl_prepare_ctx(mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { xdl_free_classifier(&cf); return -1; } - if (xdl_prepare_ctx(mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { xdl_free_ctx(&xe->xdf1); xdl_free_classifier(&cf); return -1; } - if (!(xpp->flags & XDF_HISTOGRAM_DIFF)) - xdl_free_classifier(&cf); - if (!(xpp->flags & XDF_PATIENCE_DIFF) && !(xpp->flags & XDF_HISTOGRAM_DIFF) && - xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) { + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { xdl_free_ctx(&xe->xdf2); xdl_free_ctx(&xe->xdf1); return -1; } + if (!(xpp->flags & XDF_HISTOGRAM_DIFF)) + xdl_free_classifier(&cf); + return 0; } @@ -355,11 +382,10 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { * matches on the other file. Also, lines that have multiple matches * might be potentially discarded if they happear in a run of discardable. */ -static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, rhi, nreff, mlim; - unsigned long hav; +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff; xrecord_t **recs; - xrecord_t *rec; + xdlclass_t *rcrec; char *dis, *dis1, *dis2; if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { @@ -370,26 +396,16 @@ static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) { dis1 = dis; dis2 = dis1 + xdf1->nrec + 1; - if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { - hav = (*recs)->ha; - rhi = (long) XDL_HASHLONG(hav, xdf2->hbits); - for (nm = 0, rec = xdf2->rhash[rhi]; rec; rec = rec->next) - if (rec->ha == hav && ++nm == mlim) - break; - dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: 1; } - if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { - hav = (*recs)->ha; - rhi = (long) XDL_HASHLONG(hav, xdf1->hbits); - for (nm = 0, rec = xdf1->rhash[rhi]; rec; rec = rec->next) - if (rec->ha == hav && ++nm == mlim) - break; - dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: 1; } for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; @@ -451,10 +467,10 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { } -static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) { +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { if (xdl_trim_ends(xdf1, xdf2) < 0 || - xdl_cleanup_records(xdf1, xdf2) < 0) { + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { return -1; } |