diff options
142 files changed, 5160 insertions, 1327 deletions
diff --git a/.gitignore b/.gitignore index fcdd822d8a..4cb14e0bae 100644 --- a/.gitignore +++ b/.gitignore @@ -166,12 +166,17 @@ /test-dump-cache-tree /test-genrandom /test-index-version +/test-line-buffer /test-match-trees +/test-obj-pool /test-parse-options /test-path-utils /test-run-command /test-sha1 /test-sigchain +/test-string-pool +/test-svn-fe +/test-treap /common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/RelNotes-1.7.2.3.txt b/Documentation/RelNotes-1.7.2.3.txt new file mode 100644 index 0000000000..610960cfe1 --- /dev/null +++ b/Documentation/RelNotes-1.7.2.3.txt @@ -0,0 +1,39 @@ +Git v1.7.2.3 Release Notes +========================== + +Fixes since v1.7.2.2 +-------------------- + + * When people try insane things such as delta-compressing 4GiB files, we + threw an assertion failure. + + * "git archive" gave the full commit ID for "$Format:%h$". + + * "git fetch --tags" did not fetch tags when remote.<nick>.tagopt was set + to --no-tags. The command line option now overrides the configuration + setting. + + * "git for-each-ref --format='%(objectname:short)'" has been completely + broken for a long time. + + * "git gc" incorrectly pruned a rerere record that was created long + time ago but still is actively and repeatedly used. + + * "git log --follow -M -p" was seriously broken in 1.7.2, reporting + assertion failure. + + * Running "git log" with an incorrect option started pager nevertheless, + forcing the user to dismiss it. + + * "git rebase" did not work well when the user has diff.renames + configuration variable set. + + * An earlier (and rather old) fix to "git rebase" against a rebased + upstream broke a more normal, non rebased upstream case rather badly, + attempting to re-apply patches that are already accepted upstream. + + * "git submodule sync" forgot to update the superproject's config file + when submodule URL changed. + + * "git pack-refs --all --prune" did not remove a directory that has + become empty. diff --git a/Documentation/RelNotes-1.7.3.txt b/Documentation/RelNotes-1.7.3.txt new file mode 100644 index 0000000000..3512bbb238 --- /dev/null +++ b/Documentation/RelNotes-1.7.3.txt @@ -0,0 +1,73 @@ +Git v1.7.3 Release Notes (draft) +================================ + +Updates since v1.7.2 +-------------------- + + * git-gui got various updates and a new maintainer, Pat Thoyts. + + * Gitweb allows its configuration to change per each request; it used to + read the configuration once upon startup. + + * When git finds a corrupt object, it now reports the file that contains + it. + + * "git checkout -B <it>" is a shorter way to say "git branch -f <it>" + followed by "git checkout <it>". + + * When "git checkout" or "git merge" refuse to proceed in order to + protect local modification to your working tree, they used to stop + after showing just one path that might be lost. They now show all, + in a format that is easier to read. + + * "git clean" learned "-e" ("--exclude") option. + + * Hunk headers produced for C# files by "git diff" and friends show more + relevant context than before. + + * diff.ignoresubmodules configuration variable can be used to squelch the + differences in submodules reported when running commands (e.g. "diff", + "status", etc.) at the superproject level. + + * http.useragent configuration can be used to lie who you are to your + restrictive firewall. + + * "git rebase --strategy <s>" learned "-X" option to pass extra options + that are understood by the chosen merge strategy. + + * "git rebase -i" learned "exec" that you can insert into the insn sheet + to run a command between its steps. + + * "git rebase" between branches that have many binary changes that do + not conflict should be faster. + + * "git rebase -i" peeks into rebase.autosquash configuration and acts as + if you gave --autosquash from the command line. + + +Also contains various documentation updates. + + +Fixes since v1.7.2 +------------------ + +All of the fixes in v1.7.2.X maintenance series are included in this +release, unless otherwise noted. + + * "git merge -s recursive" (which is the default) did not handle cases + where a directory becomes a file (or vice versa) very well. + + * "git fetch" and friends were accidentally broken for url with "+" in + its path, e.g. "git://git.gnome.org/gtk+". + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.7.2.2-268-g7e42332 +O=v1.7.2 +git shortlog --no-merges $O..master ^maint +exit 0 + +What did we want to do with... + +1e3d411 (Enable custom schemes for column colors in the graph API, 2010-07-13) diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 87a90f2c3f..b5f0f29d1f 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -16,7 +16,9 @@ plus=+ caret=^ startsb=[ endsb=] +backslash=\ tilde=~ +apostrophe=' backtick=` ifdef::backend-docbook[] diff --git a/Documentation/config.txt b/Documentation/config.txt index 05ec3fed89..0510ac795c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1305,10 +1305,11 @@ interactive.singlekey:: ignored if portable keystroke input is not available. log.date:: - Set default date-time mode for the log command. Setting log.date - value is similar to using 'git log'\'s --date option. The value is one of the - following alternatives: {relative,local,default,iso,rfc,short}. - See linkgit:git-log[1]. + Set the default date-time mode for the 'log' command. + Setting a value for log.date is similar to using 'git log''s + `\--date` option. Possible values are `relative`, `local`, + `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1] + for details. log.decorate:: Print out the ref names of any commits that are shown by the log @@ -1549,6 +1550,9 @@ rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default. +rebase.autosquash:: + If set to true enable '--autosquash' option by default. + receive.autogc:: By default, git-receive-pack will run "git-gc --auto" after receiving data from git-push and updating refs. You can stop @@ -1643,7 +1647,9 @@ remote.<name>.tagopt:: Setting this value to \--no-tags disables automatic tag following when fetching from remote <name>. Setting it to \--tags will fetch every tag from remote <name>, even if they are not reachable from remote - branch heads. + branch heads. Passing these flags directly to linkgit:git-fetch[1] can + override this setting. See options \--tags and \--no-tags of + linkgit:git-fetch[1]. remote.<name>.vcs:: Setting this to a value <vcs> will cause git to interact with diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 9333c42c55..470ac31396 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -49,7 +49,9 @@ ifndef::git-pull[] endif::git-pull[] By default, tags that point at objects that are downloaded from the remote repository are fetched and stored locally. - This option disables this automatic tag following. + This option disables this automatic tag following. The default + behavior for a remote may be specified with the remote.<name>.tagopt + setting. See linkgit:git-config[1]. -t:: --tags:: @@ -58,7 +60,9 @@ endif::git-pull[] objects reachable from the branch heads that are being tracked will not be fetched by this mechanism. This flag lets all tags and their associated objects be - downloaded. + downloaded. The default behavior for a remote may be + specified with the remote.<name>.tagopt setting. See + linkgit:git-config[1]. -u:: --update-head-ok:: diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index e22a62f065..e213a2efd7 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -157,14 +157,14 @@ those in info/exclude. See linkgit:gitrepository-layout[5]. EXAMPLES -------- -* Adds content from all `\*.txt` files under `Documentation` directory +* Adds content from all `*.txt` files under `Documentation` directory and its subdirectories: + ------------ $ git add Documentation/\*.txt ------------ + -Note that the asterisk `\*` is quoted from the shell in this +Note that the asterisk `*` is quoted from the shell in this example; this lets the command include the files from subdirectories of `Documentation/` directory. diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index efbe3790bb..8a2ba37904 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -873,7 +873,7 @@ c * N * T + b * M * log2(M) tests where c is the number of rounds of test (so a small constant) and b is the ratio of bug per commit (hopefully a small constant too). -So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if +So of course it's much better as it's O(N * T) vs O(N * T * M) if you would test everything after each commit. This means that test suites are good to prevent some bugs from being diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index d6aa6e14eb..62f9ab24c9 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -13,7 +13,7 @@ SYNOPSIS [--stage=<number>|all] [--temp] [-z] [--stdin] - [--] [<file>]\* + [--] [<file>]* DESCRIPTION ----------- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 66e570113a..f88e9977d1 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -187,7 +187,7 @@ As a special case, the `"@\{-N\}"` syntax for the N-th last branch checks out the branch (instead of detaching). You may also specify `-` which is synonymous with `"@\{-1\}"`. + -As a further special case, you may use `"A...B"` as a shortcut for the +As a further special case, you may use `"A\...B"` as a shortcut for the merge base of `A` and `B` if there is exactly one merge base. You can leave out at most one of `A` and `B`, in which case it defaults to `HEAD`. diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 61888547a1..349366ee1e 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -8,7 +8,7 @@ git-commit-tree - Create a new commit object SYNOPSIS -------- -'git commit-tree' <tree> [-p <parent commit>]\* < changelog +'git commit-tree' <tree> [-p <parent commit>]* < changelog DESCRIPTION ----------- diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 98ec6b5871..fcad113276 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -90,10 +90,16 @@ marks the same across runs. resulting stream can only be used by a repository which already contains the necessary objects. +--full-tree:: + This option will cause fast-export to issue a "deleteall" + directive for each commit followed by a full list of all files + in the commit (as opposed to just listing the files which are + different from the commit's first parent). + [git-rev-list-args...]:: A list of arguments, acceptable to 'git rev-parse' and 'git rev-list', that specifies the specific objects and references - to export. For example, `master\~10..master` causes the + to export. For example, `master{tilde}10..master` causes the current master reference to be exported along with all objects added since its 10th ancestor commit. diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index a585dbe898..302f56b889 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message SYNOPSIS -------- [verse] -'git fmt-merge-msg' [--log | --no-log] <$GIT_DIR/FETCH_HEAD -'git fmt-merge-msg' [--log | --no-log] -F <file> +'git fmt-merge-msg' [-m <message>] [--log | --no-log] <$GIT_DIR/FETCH_HEAD +'git fmt-merge-msg' [-m <message>] [--log | --no-log] -F <file> DESCRIPTION ----------- @@ -38,6 +38,11 @@ OPTIONS Synonyms to --log and --no-log; these are deprecated and will be removed in the future. +-m <message>:: +--message <message>:: + Use <message> instead of the branch names for the first line + of the log message. For use with `--log`. + -F <file>:: --file <file>:: Take the list of merged objects from <file> instead of diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 390d85ccae..d66fd9d231 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] - [--sort=<key>]\* [--format=<format>] [<pattern>...] + [--sort=<key>]* [--format=<format>] [<pattern>...] DESCRIPTION ----------- diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 5474dd7f94..dab0a78fa8 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -191,11 +191,11 @@ OPTIONS Examples -------- -git grep 'time_t' \-- '*.[ch]':: +git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}:: Looks for `time_t` in all tracked .c and .h files in the working directory and its subdirectories. -git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: +git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \):: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index a7c8174d01..15aee2f953 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -10,14 +10,14 @@ SYNOPSIS -------- [verse] 'git ls-files' [-z] [-t] [-v] - (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\* - (-[c|d|o|i|s|u|k|m])\* + (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])* + (-[c|d|o|i|s|u|k|m])* [-x <pattern>|--exclude=<pattern>] [-X <file>|--exclude-from=<file>] [--exclude-per-directory=<file>] [--exclude-standard] [--error-unmatch] [--with-tree=<tree-ish>] - [--full-name] [--abbrev] [--] [<file>]\* + [--full-name] [--abbrev] [--] [<file>]* DESCRIPTION ----------- diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index ce5b369985..eedef1bb1a 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -8,7 +8,9 @@ git-merge-base - Find as good common ancestors as possible for a merge SYNOPSIS -------- -'git merge-base' [-a|--all] <commit> <commit>... +[verse] +'git merge-base' [-a|--all] [--octopus] <commit> <commit>... +'git merge-base' --independent <commit>... DESCRIPTION ----------- @@ -20,12 +22,12 @@ that does not have any better common ancestor is a 'best common ancestor', i.e. a 'merge base'. Note that there can be more than one merge base for a pair of commits. -Among the two commits to compute the merge base from, one is specified by -the first commit argument on the command line; the other commit is a -(possibly hypothetical) commit that is a merge across all the remaining -commits on the command line. As the most common special case, specifying only -two commits on the command line means computing the merge base between -the given two commits. +Unless `--octopus` is given, among the two commits to compute the merge +base from, one is specified by the first commit argument on the command +line; the other commit is a (possibly hypothetical) commit that is a merge +across all the remaining commits on the command line. As the most common +special case, specifying only two commits on the command line means +computing the merge base between the given two commits. As a consequence, the 'merge base' is not necessarily contained in each of the commit arguments if more than two commits are specified. This is different @@ -37,6 +39,18 @@ OPTIONS --all:: Output all merge bases for the commits, instead of just one. +--octopus:: + Compute the best common ancestors of all supplied commits, + in preparation for an n-way merge. This mimics the behavior + of 'git show-branch --merge-base'. + +--independent:: + Instead of printing merge bases, print a minimal subset of + the supplied commits with the same ancestors. In other words, + among the commits given, list those which cannot be reached + from any other. This mimics the behavior of 'git show-branch + --independent'. + DISCUSSION ---------- @@ -96,6 +110,12 @@ Documentation -------------- Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. +See also +-------- +linkgit:git-rev-list[1], +linkgit:git-show-branch[1], +linkgit:git-merge[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 4d266de9cc..921b38f183 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -8,7 +8,7 @@ git-merge-index - Run a merge for files needing merging SYNOPSIS -------- -'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*) +'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*) DESCRIPTION ----------- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 658ff2ff67..020955ff5a 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -200,7 +200,7 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `<old>..<new>` in most cases, and - `<old>...<new>` for forced non-fast-forward updates). + `<old>\...<new>` for forced non-fast-forward updates). + For a failed update, more details are given: + diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 3b87f1a1b6..30e5c0eb14 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -199,6 +199,9 @@ rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default. +rebase.autosquash:: + If set to true enable '--autosquash' option by default. + OPTIONS ------- <newbase>:: @@ -207,7 +210,7 @@ OPTIONS <upstream>. May be any valid commit, and not just an existing branch name. + -As a special case, you may use "A...B" as a shortcut for the +As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. @@ -333,6 +336,7 @@ idea unless you know what you are doing (see BUGS below). instead. --autosquash:: +--no-autosquash:: When the commit log message begins with "squash! ..." (or "fixup! ..."), and there is a commit whose title begins with the same ..., automatically modify the todo list of rebase -i @@ -341,6 +345,10 @@ idea unless you know what you are doing (see BUGS below). commit from `pick` to `squash` (or `fixup`). + This option is only valid when the '--interactive' option is used. ++ +If the '--autosquash' option is enabled by default using the +configuration variable `rebase.autosquash`, this option can be +used to override and disable this setting. --no-ff:: With --interactive, cherry-pick all rebased commits instead of diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt index 25ff8f9dcb..8a5842bb93 100644 --- a/Documentation/git-relink.txt +++ b/Documentation/git-relink.txt @@ -7,7 +7,7 @@ git-relink - Hardlink common objects in local repositories SYNOPSIS -------- -'git relink' [--safe] <dir> [<dir>]\* <master_dir> +'git relink' [--safe] <dir> [<dir>]* <master_dir> DESCRIPTION ----------- diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index be4c053360..341ca90c6e 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -74,7 +74,7 @@ OPTIONS properly quoted for consumption by shell. Useful when you expect your parameter to contain whitespaces and newlines (e.g. when using pickaxe `-S` with - 'git diff-\*'). In contrast to the `--sq-quote` option, + 'git diff-{asterisk}'). In contrast to the `--sq-quote` option, the command input is still interpreted as usual. --not:: @@ -112,14 +112,15 @@ OPTIONS + If a `pattern` is given, only refs matching the given shell glob are shown. If the pattern does not contain a globbing character (`?`, -`\*`, or `[`), it is turned into a prefix match by appending `/\*`. +`{asterisk}`, or `[`), it is turned into a prefix match by +appending `/{asterisk}`. --glob=pattern:: Show all refs matching the shell glob pattern `pattern`. If the pattern does not start with `refs/`, this is automatically prepended. If the pattern does not contain a globbing - character (`?`, `\*`, or `[`), it is turned into a prefix - match by appending `/\*`. + character (`?`, `{asterisk}`, or `[`), it is turned into a prefix + match by appending `/{asterisk}`. --show-toplevel:: Show the absolute path of the top-level directory. diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index c21d19e573..71e3d9fc23 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -78,7 +78,8 @@ a file that you have not told git about does not remove that file. File globbing matches across directory boundaries. Thus, given two directories `d` and `d2`, there is a difference between -using `git rm \'d\*\'` and `git rm \'d/\*\'`, as the former will +using `git rm {apostrophe}d{asterisk}{apostrophe}` and +`git rm {apostrophe}d/{asterisk}{apostrophe}`, as the former will also remove all of directory `d2`. REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM @@ -135,11 +136,11 @@ git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached EXAMPLES -------- -git rm Documentation/\\*.txt:: - Removes all `\*.txt` files from the index that are under the +git rm Documentation/\*.txt:: + Removes all `*.txt` files from the index that are under the `Documentation` directory and any of its subdirectories. + -Note that the asterisk `\*` is quoted from the shell in this +Note that the asterisk `*` is quoted from the shell in this example; this lets git, and not the shell, expand the pathnames of files and subdirectories under the `Documentation/` directory. diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 81ba29669c..6453263340 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -168,10 +168,10 @@ $ git show-branch master fixes mhf ------------------------------------------------ These three branches all forked from a common commit, [master], -whose commit message is "Add \'git show-branch\'". The "fixes" -branch adds one commit "Introduce "reset type" flag to "git reset"". -The "mhf" branch adds many other commits. The current branch -is "master". +whose commit message is "Add {apostrophe}git show-branch{apostrophe}". +The "fixes" branch adds one commit "Introduce "reset type" flag to +"git reset"". The "mhf" branch adds many other commits. +The current branch is "master". EXAMPLE diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 75780d7d63..4696af7433 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -73,8 +73,8 @@ OPTIONS --exclude-existing[=<pattern>]:: Make 'git show-ref' act as a filter that reads refs from stdin of the - form "^(?:<anything>\s)?<refname>(?:\^\{\})?$" and performs the - following actions on each: + form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$" + and performs the following actions on each: (1) strip "^{}" at the end of line if any; (2) ignore if pattern is provided and does not head-match refname; (3) warn if refname is not a well-formed refname and skip; diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 765d4b312e..74d1d49dbf 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git update-index' [--add] [--remove | --force-remove] [--replace] [--refresh] [-q] [--unmerged] [--ignore-missing] - [--cacheinfo <mode> <object> <file>]\* + [--cacheinfo <mode> <object> <file>]* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] [--skip-worktree | --no-skip-worktree] @@ -21,7 +21,7 @@ SYNOPSIS [--info-only] [--index-info] [-z] [--stdin] [--verbose] - [--] [<file>]\* + [--] [<file>]* DESCRIPTION ----------- diff --git a/Documentation/git.txt b/Documentation/git.txt index 531789321c..93e3b07c6c 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.2.2/git.html[documentation for release 1.7.2.2] +* link:v1.7.2.3/git.html[documentation for release 1.7.2.3] * release notes for + link:RelNotes-1.7.2.3.txt[1.7.2.3], link:RelNotes-1.7.2.2.txt[1.7.2.2], link:RelNotes-1.7.2.1.txt[1.7.2.1], link:RelNotes-1.7.2.txt[1.7.2]. diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 2e2370ccdb..e5a27d875e 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -317,6 +317,17 @@ command is "cat"). smudge = cat ------------------------ +For best results, `clean` should not alter its output further if it is +run twice ("clean->clean" should be equivalent to "clean"), and +multiple `smudge` commands should not alter `clean`'s output +("smudge->smudge->clean" should be equivalent to "clean"). See the +section on merging below. + +The "indent" filter is well-behaved in this regard: it will not modify +input that is already correctly indented. In this case, the lack of a +smudge filter means that the clean filter _must_ accept its own output +without modifying it. + Interaction between checkin/checkout attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -331,6 +342,29 @@ In the check-out codepath, the blob content is first converted with `text`, and then `ident` and fed to `filter`. +Merging branches with differing checkin/checkout attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you have added attributes to a file that cause the canonical +repository format for that file to change, such as adding a +clean/smudge filter or text/eol/ident attributes, merging anything +where the attribute is not in place would normally cause merge +conflicts. + +To prevent these unnecessary merge conflicts, git can be told to run a +virtual check-out and check-in of all three stages of a file when +resolving a three-way merge by setting the `merge.renormalize` +configuration variable. This prevents changes caused by check-in +conversion from causing spurious merge conflicts when a converted file +is merged with an unconverted file. + +As long as a "smudge->clean" results in the same output as a "clean" +even on files that are already smudged, this strategy will +automatically resolve all filter-related conflicts. Filters that do +not act in this way may cause additional merge conflicts that must be +resolved manually. + + Generating diff text ~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index ed3ddc92cb..5e9c5ebba3 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -110,7 +110,7 @@ An 'object' is identified by its 160-bit SHA1 hash, aka 'object name', and a reference to an object is always the 40-byte hex representation of that SHA1 name. The files in the `refs` subdirectory are expected to contain these hex references -(usually with a final `\'\n\'` at the end), and you should thus +(usually with a final `\n` at the end), and you should thus expect to see a number of 41-byte files containing these references in these `refs` subdirectories when you actually start populating your tree. @@ -310,7 +310,7 @@ and this will just output the name of the resulting tree, in this case ---------------- which is another incomprehensible object name. Again, if you want to, -you can use `git cat-file -t 8988d\...` to see that this time the object +you can use `git cat-file -t 8988d...` to see that this time the object is not a "blob" object, but a "tree" object (you can also use `git cat-file` to actually output the raw object contents, but you'll see mainly a binary mess, so that's less interesting). @@ -436,8 +436,8 @@ $ git update-index hello (note how we didn't need the `\--add` flag this time, since git knew about the file already). -Note what happens to the different 'git diff-\*' versions here. After -we've updated `hello` in the index, `git diff-files -p` now shows no +Note what happens to the different 'git diff-{asterisk}' versions here. +After we've updated `hello` in the index, `git diff-files -p` now shows no differences, but `git diff-index -p HEAD` still *does* show that the current state is different from the state we committed. In fact, now 'git diff-index' shows the same difference whether we use the `--cached` @@ -494,7 +494,7 @@ and it will show what the last commit (in `HEAD`) actually changed. [NOTE] ============ Here is an ASCII art by Jon Loeliger that illustrates how -various diff-\* commands compare things. +various 'diff-{asterisk}' commands compare things. diff-tree +----+ @@ -958,11 +958,11 @@ $ git show-branch --topo-order --more=1 master mybranch The first two lines indicate that it is showing the two branches and the first line of the commit log message from their top-of-the-tree commits, you are currently on `master` branch -(notice the asterisk `\*` character), and the first column for +(notice the asterisk `{asterisk}` character), and the first column for the later output lines is used to show commits contained in the `master` branch, and the second column for the `mybranch` branch. Three commits are shown along with their log messages. -All of them have non blank characters in the first column (`*` +All of them have non blank characters in the first column (`{asterisk}` shows an ordinary commit on the current branch, `-` is a merge commit), which means they are now part of the `master` branch. Only the "Some work" commit has the plus `+` character in the second column, @@ -1092,7 +1092,7 @@ Downloader from http and https URL first obtains the topmost commit object name from the remote site by looking at the specified refname under `repo.git/refs/` directory, and then tries to obtain the -commit object by downloading from `repo.git/objects/xx/xxx\...` +commit object by downloading from `repo.git/objects/xx/xxx...` using the object name of that commit object. Then it reads the commit object to find out its parent commits and the associate tree object; it repeats this process until it gets all the @@ -1420,7 +1420,7 @@ packed, and stores the packed file in `.git/objects/pack` directory. [NOTE] -You will see two files, `pack-\*.pack` and `pack-\*.idx`, +You will see two files, `pack-{asterisk}.pack` and `pack-{asterisk}.idx`, in `.git/objects/pack` directory. They are closely related to each other, and if you ever copy them by hand to a different repository for whatever reason, you should make sure you copy diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index e10fa88b8c..7dc2e8b0bc 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -90,12 +90,12 @@ Patterns have the following format: - Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. - For example, "Documentation/\*.html" matches + For example, "Documentation/{asterisk}.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html". - A leading slash matches the beginning of the pathname. - For example, "/*.c" matches "cat-file.c" but not + For example, "/{asterisk}.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". An example: diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt index 8c32da6deb..093c656048 100644 --- a/Documentation/howto/revert-branch-rebase.txt +++ b/Documentation/howto/revert-branch-rebase.txt @@ -112,25 +112,19 @@ $ git tag pu-anchor pu $ git rebase master * Applying: Redo "revert" using three-way merge machinery. First trying simple merge strategy to cherry-pick. -Finished one cherry-pick. * Applying: Remove git-apply-patch-script. First trying simple merge strategy to cherry-pick. Simple cherry-pick fails; trying Automatic cherry-pick. Removing Documentation/git-apply-patch-script.txt Removing git-apply-patch-script -Finished one cherry-pick. * Applying: Document "git cherry-pick" and "git revert" First trying simple merge strategy to cherry-pick. -Finished one cherry-pick. * Applying: mailinfo and applymbox updates First trying simple merge strategy to cherry-pick. -Finished one cherry-pick. * Applying: Show commits in topo order and name all commits. First trying simple merge strategy to cherry-pick. -Finished one cherry-pick. * Applying: More documentation updates. First trying simple merge strategy to cherry-pick. -Finished one cherry-pick. ------------------------------------------------ The temporary tag 'pu-anchor' is me just being careful, in case 'git diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh index 34d02a2418..37e67d1a14 100755 --- a/Documentation/install-webdoc.sh +++ b/Documentation/install-webdoc.sh @@ -12,7 +12,7 @@ do then : did not match elif test -f "$T/$h" && - $DIFF -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" + $DIFF -u -I'^Last updated ' "$T/$h" "$h" then :; # up to date else diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index a403155052..b72f533970 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -15,6 +15,16 @@ merge.renameLimit:: during a merge; if not specified, defaults to the value of diff.renameLimit. +merge.renormalize:: + Tell git that canonical representation of files in the + repository has changed over time (e.g. earlier commits record + text files with CRLF line endings, but recent ones use LF line + endings). In such a repository, git can convert the data + recorded in commits to a canonical form before performing a + merge to reduce unnecessary conflicts. For more information, + see section "Merging branches with differing checkin/checkout + attributes" in linkgit:gitattributes[5]. + merge.stat:: Whether to print the diffstat between ORIG_HEAD and the merge result at the end of the merge. True by default. diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index a5bc1dbb95..049313d601 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -40,6 +40,18 @@ the other tree did, declaring 'our' history contains all that happened in it. theirs;; This is opposite of 'ours'. +renormalize;; + This runs a virtual check-out and check-in of all three stages + of a file when resolving a three-way merge. This option is + meant to be used when merging branches with different clean + filters or end-of-line normalization rules. See "Merging + branches with differing checkin/checkout attributes" in + linkgit:gitattributes[5] for details. + +no-renormalize;; + Disables the `renormalize` option. This overrides the + `merge.renormalize` configuration variable. + subtree[=path];; This option is a more advanced form of 'subtree' strategy, where the strategy makes a guess on how two trees must be shifted to diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index cc562a057a..e2237ae4a0 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -321,7 +321,7 @@ excluded from the output. reflog entries from the most recent one to older ones. When this option is used you cannot specify commits to exclude (that is, '{caret}commit', 'commit1..commit2', - nor 'commit1...commit2' notations cannot be used). + nor 'commit1\...commit2' notations cannot be used). + With '\--pretty' format other than oneline (for obvious reasons), this causes the output to have two extra lines of information diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt new file mode 100644 index 0000000000..a7e050bb7a --- /dev/null +++ b/Documentation/technical/api-merge.txt @@ -0,0 +1,73 @@ +merge API +========= + +The merge API helps a program to reconcile two competing sets of +improvements to some files (e.g., unregistered changes from the work +tree versus changes involved in switching to a new branch), reporting +conflicts if found. The library called through this API is +responsible for a few things. + + * determining which trees to merge (recursive ancestor consolidation); + + * lining up corresponding files in the trees to be merged (rename + detection, subtree shifting), reporting edge cases like add/add + and rename/rename conflicts to the user; + + * performing a three-way merge of corresponding files, taking + path-specific merge drivers (specified in `.gitattributes`) + into account. + +Low-level (single file) merge +----------------------------- + +`ll_merge`:: + + Perform a three-way single-file merge in core. This is + a thin wrapper around `xdl_merge` that takes the path and + any merge backend specified in `.gitattributes` or + `.git/info/attributes` into account. Returns 0 for a + clean merge. + +The caller: + +1. allocates an mmbuffer_t variable for the result; +2. allocates and fills variables with the file's original content + and two modified versions (using `read_mmfile`, for example); +3. calls ll_merge(); +4. reads the output from result_buf.ptr and result_buf.size; +5. releases buffers when finished (free(ancestor.ptr); free(ours.ptr); + free(theirs.ptr); free(result_buf.ptr);). + +If the modifications do not merge cleanly, `ll_merge` will return a +nonzero value and `result_buf` will generally include a description of +the conflict bracketed by markers such as the traditional `<<<<<<<` +and `>>>>>>>`. + +The `ancestor_label`, `our_label`, and `their_label` parameters are +used to label the different sides of a conflict if the merge driver +supports this. + +The `flag` parameter is a bitfield: + + - The `LL_OPT_VIRTUAL_ANCESTOR` bit indicates whether this is an + internal merge to consolidate ancestors for a recursive merge. + + - The `LL_OPT_FAVOR_MASK` bits allow local conflicts to be automatically + resolved in favor of one side or the other (as in 'git merge-file' + `--ours`/`--theirs`/`--union`). + They can be populated by `create_ll_flag`, whose argument can be + `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or + `XDL_MERGE_FAVOR_UNION`. + +Everything else +--------------- + +Talk about <merge-recursive.h> and merge_file(): + + - merge_trees() to merge with rename detection + - merge_recursive() for ancestor consolidation + - try_merge_command() for other strategies + - conflict format + - merge options + +(Daniel, Miklos, Stephan, JC) diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 312e3b2e2b..c5d141cd63 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -201,7 +201,7 @@ The last element of the array must be `OPT_END()`. If not stated otherwise, interpret the arguments as follows: * `short` is a character for the short option - (e.g. `\'e\'` for `-e`, use `0` to omit), + (e.g. `{apostrophe}e{apostrophe}` for `-e`, use `0` to omit), * `long` is a string for the long option (e.g. `"example"` for `\--example`, use `NULL` to omit), @@ -228,10 +228,10 @@ The function must be defined in this form: The callback mechanism is as follows: * Inside `func`, the only interesting member of the structure - given by `opt` is the void pointer `opt->value`. - `\*opt->value` will be the value that is saved into `var`, if you + given by `opt` is the void pointer `opt\->value`. + `\*opt\->value` will be the value that is saved into `var`, if you use `OPT_CALLBACK()`. - For example, do `*(unsigned long *)opt->value = 42;` to get 42 + For example, do `*(unsigned long *)opt\->value = 42;` to get 42 into an `unsigned long` variable. * Return value `0` indicates success and non-zero return diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 22aee34d4a..fecc4eb5b3 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4251,9 +4251,9 @@ Two things are interesting here: negative numbers in case of different errors--and 0 on success. - the variable `sha1` in the function signature of `get_sha1()` is `unsigned - char \*`, but is actually expected to be a pointer to `unsigned + char {asterisk}`, but is actually expected to be a pointer to `unsigned char[20]`. This variable will contain the 160-bit SHA-1 of the given - commit. Note that whenever a SHA-1 is passed as `unsigned char \*`, it + commit. Note that whenever a SHA-1 is passed as `unsigned char {asterisk}`, it is the binary representation, as opposed to the ASCII representation in hex characters, which is passed as `char *`. @@ -68,6 +68,8 @@ all:: # # Define NO_MKSTEMPS if you don't have mkstemps in the C library. # +# Define NO_STRTOK_R if you don't have strtok_r in the C library. +# # Define NO_LIBGEN_H if you don't have libgen.h. # # Define NEEDS_LIBGEN if your libgen needs -lgen when linking @@ -408,12 +410,17 @@ TEST_PROGRAMS_NEED_X += test-date TEST_PROGRAMS_NEED_X += test-delta TEST_PROGRAMS_NEED_X += test-dump-cache-tree TEST_PROGRAMS_NEED_X += test-genrandom +TEST_PROGRAMS_NEED_X += test-line-buffer TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-obj-pool TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sigchain +TEST_PROGRAMS_NEED_X += test-string-pool +TEST_PROGRAMS_NEED_X += test-svn-fe +TEST_PROGRAMS_NEED_X += test-treap TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@ -468,6 +475,7 @@ export PYTHON_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a +VCSSVN_LIB=vcs-svn/lib.a LIB_H += advice.h LIB_H += archive.h @@ -1035,6 +1043,7 @@ ifeq ($(uname_S),Windows) NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease NO_MEMMEM = YesPlease # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease @@ -1089,6 +1098,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease @@ -1319,6 +1329,10 @@ endif ifdef NO_STRTOULL COMPAT_CFLAGS += -DNO_STRTOULL endif +ifdef NO_STRTOK_R + COMPAT_CFLAGS += -DNO_STRTOK_R + COMPAT_OBJS += compat/strtok_r.o +endif ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o @@ -1739,7 +1753,9 @@ ifndef NO_CURL endif XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o -OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) +VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \ + vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o +OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) @@ -1861,6 +1877,11 @@ http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h xdiff-interface.o $(XDIFF_OBJS): \ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h + +$(VCSSVN_OBJS): \ + vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \ + vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \ + vcs-svn/svndump.h endif exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ @@ -1909,6 +1930,8 @@ $(LIB_FILE): $(LIB_OBJS) $(XDIFF_LIB): $(XDIFF_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) +$(VCSSVN_LIB): $(VCSSVN_OBJS) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS) doc: $(MAKE) -C Documentation all @@ -2007,12 +2030,18 @@ test-date$X: date.o ctype.o test-delta$X: diff-delta.o patch-delta.o +test-line-buffer$X: vcs-svn/lib.a + test-parse-options$X: parse-options.o +test-string-pool$X: vcs-svn/lib.a + +test-svn-fe$X: vcs-svn/lib.a + .PRECIOUS: $(TEST_OBJS) test-%$X: test-%.o $(GITLIBS) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) check-sha1:: test-sha1$X ./test-sha1.sh @@ -2187,8 +2216,8 @@ distclean: clean $(RM) configure clean: - $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ - builtin/*.o $(LIB_FILE) $(XDIFF_LIB) + $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ + builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers @@ -1 +1 @@ -Documentation/RelNotes-1.7.2.2.txt
\ No newline at end of file +Documentation/RelNotes-1.7.3.txt
\ No newline at end of file diff --git a/builtin/apply.c b/builtin/apply.c index f38c1f7b88..470520bde7 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -3606,11 +3606,11 @@ static int option_parse_directory(const struct option *opt, return 0; } -int cmd_apply(int argc, const char **argv, const char *unused_prefix) +int cmd_apply(int argc, const char **argv, const char *prefix_) { int i; int errs = 0; - int is_not_gitdir; + int is_not_gitdir = !startup_info->have_repository; int binary; int force_apply = 0; @@ -3683,7 +3683,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) OPT_END() }; - prefix = setup_git_directory_gently(&is_not_gitdir); + prefix = prefix_; prefix_length = prefix ? strlen(prefix) : 0; git_config(git_apply_config, NULL); if (apply_default_whitespace) diff --git a/builtin/bundle.c b/builtin/bundle.c index 2006cc5cd5..80649ba0b2 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -18,7 +18,6 @@ static const char builtin_bundle_usage[] = int cmd_bundle(int argc, const char **argv, const char *prefix) { struct bundle_header header; - int nongit; const char *cmd, *bundle_file; int bundle_fd = -1; char buffer[PATH_MAX]; @@ -31,7 +30,6 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) argc -= 2; argv += 2; - prefix = setup_git_directory_gently(&nongit); if (prefix && bundle_file[0] != '/') { snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); bundle_file = buffer; @@ -54,11 +52,11 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) return !!list_bundle_refs(&header, argc, argv); } if (!strcmp(cmd, "create")) { - if (nongit) + if (!startup_info->have_repository) die("Need a repository to create a bundle."); return !!create_bundle(&header, bundle_file, argc, argv); } else if (!strcmp(cmd, "unbundle")) { - if (nongit) + if (!startup_info->have_repository) die("Need a repository to unbundle."); return !!unbundle(&header, bundle_fd) || list_bundle_refs(&header, argc, argv); diff --git a/builtin/checkout.c b/builtin/checkout.c index 7250e5c23c..ff5ac1e0ff 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -154,6 +154,10 @@ static int checkout_merged(int pos, struct checkout *state) read_mmblob(&ours, active_cache[pos+1]->sha1); read_mmblob(&theirs, active_cache[pos+2]->sha1); + /* + * NEEDSWORK: re-create conflicts from merges with + * merge.renormalize set, too + */ status = ll_merge(&result_buf, path, &ancestor, "base", &ours, "ours", &theirs, "theirs", 0); free(ancestor.ptr); @@ -437,6 +441,13 @@ static int merge_working_tree(struct checkout_opts *opts, */ add_files_to_cache(NULL, NULL, 0); + /* + * NEEDSWORK: carrying over local changes + * when branches have different end-of-line + * normalization (or clean+smudge rules) is + * a pain; plumb in an option to set + * o.renormalize? + */ init_merge_options(&o); o.verbosity = 0; work = write_tree_from_memory(&o); diff --git a/builtin/config.c b/builtin/config.c index 79fee767b8..ca4a0db4a7 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -331,11 +331,10 @@ static int get_colorbool(int print) return get_colorbool_found ? 0 : 1; } -int cmd_config(int argc, const char **argv, const char *unused_prefix) +int cmd_config(int argc, const char **argv, const char *prefix) { - int nongit; + int nongit = !startup_info->have_repository; char *value; - const char *prefix = setup_git_directory_gently(&nongit); config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 834ec8b464..a9bbf8653d 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -27,6 +27,7 @@ static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; static int fake_missing_tagger; static int no_data; +static int full_tree; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -147,10 +148,39 @@ static void handle_object(const unsigned char *sha1) free(buf); } +static int depth_first(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + const char *name_a, *name_b; + int len_a, len_b, len; + int cmp; + + name_a = a->one ? a->one->path : a->two->path; + name_b = b->one ? b->one->path : b->two->path; + + len_a = strlen(name_a); + len_b = strlen(name_b); + len = (len_a < len_b) ? len_a : len_b; + + /* strcmp will sort 'd' before 'd/e', we want 'd/e' before 'd' */ + cmp = memcmp(name_a, name_b, len); + if (cmp) + return cmp; + return (len_b - len_a); +} + static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; + + /* + * Handle files below a directory first, in case they are all deleted + * and the directory changes to a file or symlink. + */ + qsort(q->queue, q->nr, sizeof(q->queue[0]), depth_first); + for (i = 0; i < q->nr; i++) { struct diff_filespec *ospec = q->queue[i]->one; struct diff_filespec *spec = q->queue[i]->two; @@ -241,7 +271,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) message += 2; if (commit->parents && - get_object_mark(&commit->parents->item->object) != 0) { + get_object_mark(&commit->parents->item->object) != 0 && + !full_tree) { parse_commit(commit->parents->item); diff_tree_sha1(commit->parents->item->tree->object.sha1, commit->tree->object.sha1, "", &rev->diffopt); @@ -281,6 +312,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) i++; } + if (full_tree) + printf("deleteall\n"); log_tree_diff_flush(rev); rev->diffopt.output_format = saved_output_format; @@ -565,7 +598,7 @@ static void import_marks(char *input_file) int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct object_array commits = { 0, 0, NULL }; + struct object_array commits = OBJECT_ARRAY_INIT; struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; @@ -584,6 +617,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) "Import marks from this file"), OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, "Fake a tagger when tags lack one"), + OPT_BOOLEAN(0, "full-tree", &full_tree, + "Output full tree for each commit"), { OPTION_NEGBIT, 0, "data", &no_data, NULL, "Skip output of blob data", PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, @@ -608,6 +643,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename) import_marks(import_filename); + if (import_filename && revs.prune_data) + full_tree = 1; + get_tags_and_duplicates(&revs.pending, &extra_refs); if (prepare_revision_walk(&revs)) diff --git a/builtin/fetch.c b/builtin/fetch.c index ea14d5dc6b..fab3fce512 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -659,10 +659,12 @@ static int do_fetch(struct transport *transport, for_each_ref(add_existing, &existing_refs); - if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) - tags = TAGS_SET; - if (transport->remote->fetch_tags == -1) - tags = TAGS_UNSET; + if (tags == TAGS_DEFAULT) { + if (transport->remote->fetch_tags == 2) + tags = TAGS_SET; + if (transport->remote->fetch_tags == -1) + tags = TAGS_UNSET; + } if (!transport->get_refs_list || !transport->fetch) die("Don't know how to fetch from %s", transport->url); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index a76cd4e9d1..e7e12eea25 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -7,7 +7,7 @@ #include "string-list.h" static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [--log|--no-log] [--file <file>]", + "git fmt-merge-msg [-m <message>] [--log|--no-log] [--file <file>]", NULL }; @@ -319,11 +319,14 @@ int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; + const char *message = NULL; struct option options[] = { OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL, "alias for --log (deprecated)", PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_STRING('m', "message", &message, "text", + "use <text> as start of message"), OPT_FILENAME('F', "file", &inpath, "file to read from"), OPT_END() }; @@ -337,6 +340,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); + if (message && !merge_summary) { + char nl = '\n'; + write_in_full(STDOUT_FILENO, message, strlen(message)); + write_in_full(STDOUT_FILENO, &nl, 1); + return 0; + } if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -346,7 +355,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (strbuf_read(&input, fileno(in), 0) < 0) die_errno("could not read input file"); - ret = fmt_merge_msg(merge_summary, &input, &output); + if (message) { + strbuf_addstr(&output, message); + ret = fmt_merge_msg_shortlog(&input, &output); + } else { + ret = fmt_merge_msg(merge_summary, &input, &output); + } if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index a2b28c6962..89e75c6894 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -228,7 +228,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object v->s = s; } else if (!strcmp(name, "objectname:short")) { - v->s = find_unique_abbrev(obj->sha1, DEFAULT_ABBREV); + v->s = xstrdup(find_unique_abbrev(obj->sha1, + DEFAULT_ABBREV)); } } } diff --git a/builtin/grep.c b/builtin/grep.c index cf6c29fa42..da32f3df34 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -834,12 +834,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int external_grep_allowed__ignored; const char *show_in_pager = NULL, *default_pager = "dummy"; struct grep_opt opt; - struct object_array list = { 0, 0, NULL }; + struct object_array list = OBJECT_ARRAY_INIT; const char **paths = NULL; struct string_list path_list = STRING_LIST_INIT_NODUP; int i; int dummy; - int nongit = 0, use_index = 1; + int use_index = 1; struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), @@ -930,8 +930,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; - prefix = setup_git_directory_gently(&nongit); - /* * 'git grep -h', unlike 'git grep -h <pattern>', is a request * to show usage information and exit. @@ -976,7 +974,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); - if (use_index && nongit) + if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ setup_git_directory(); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index fad76bf7a8..2e680d7a7a 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -886,25 +886,9 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - /* - * We wish to read the repository's config file if any, and - * for that it is necessary to call setup_git_directory_gently(). - * However if the cwd was inside .git/objects/pack/ then we need - * to go back there or all the pack name arguments will be wrong. - * And in that case we cannot rely on any prefix returned by - * setup_git_directory_gently() either. - */ - { - char cwd[PATH_MAX+1]; - int nongit; - - if (!getcwd(cwd, sizeof(cwd)-1)) - die("Unable to get current working directory"); - setup_git_directory_gently(&nongit); - git_config(git_index_pack_config, NULL); - if (chdir(cwd)) - die("Cannot come back to cwd"); - } + git_config(git_index_pack_config, NULL); + if (prefix && chdir(prefix)) + die("Cannot come back to cwd"); for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin/log.c b/builtin/log.c index 08b872263c..eaa1ee0fa7 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -125,6 +125,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->show_decorations = 1; load_ref_decorations(decoration_style); } + setup_pager(); } /* @@ -491,12 +492,6 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.use_terminator = 1; rev.always_show_header = 1; - /* - * We get called through "git reflog", so unlike the other log - * routines, we need to set up our pager manually.. - */ - setup_pager(); - return cmd_log_walk(&rev); } diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 34480cfad6..97eed4012b 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -32,7 +32,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) { int i; const char *dest = NULL; - int nongit; unsigned flags = 0; int quiet = 0; const char *uploadpack = NULL; @@ -42,8 +41,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; - setup_git_directory_gently(&nongit); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 54e7ec2237..96dd160731 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -23,7 +23,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - "git merge-base [-a|--all] <commit> <commit>...", + "git merge-base [-a|--all] [--octopus] <commit> <commit>...", + "git merge-base --independent <commit>...", NULL }; @@ -41,21 +42,58 @@ static struct commit *get_commit_reference(const char *arg) return r; } +static int handle_octopus(int count, const char **args, int reduce, int show_all) +{ + struct commit_list *revs = NULL; + struct commit_list *result; + int i; + + if (reduce) + show_all = 1; + + for (i = count - 1; i >= 0; i--) + commit_list_insert(get_commit_reference(args[i]), &revs); + + result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs); + + if (!result) + return 1; + + while (result) { + printf("%s\n", sha1_to_hex(result->item->object.sha1)); + if (!show_all) + return 0; + result = result->next; + } + + return 0; +} + int cmd_merge_base(int argc, const char **argv, const char *prefix) { struct commit **rev; int rev_nr = 0; int show_all = 0; + int octopus = 0; + int reduce = 0; struct option options[] = { - OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"), + OPT_BOOLEAN('a', "all", &show_all, "output all common ancestors"), + OPT_BOOLEAN(0, "octopus", &octopus, "find ancestors for a single n-way merge"), + OPT_BOOLEAN(0, "independent", &reduce, "list revs not reachable from others"), OPT_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); - if (argc < 2) + if (!octopus && !reduce && argc < 2) usage_with_options(merge_base_usage, options); + if (reduce && (show_all || octopus)) + die("--independent cannot be used with other options"); + + if (octopus || reduce) + return handle_octopus(argc, argv, reduce, show_all); + rev = xmalloc(argc * sizeof(*rev)); while (argc-- > 0) rev[rev_nr++] = get_commit_reference(*argv++); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index b8e9e5ba01..b6664d49be 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,7 +28,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; - int nongit; struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3), @@ -50,8 +49,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmp.style = 0; xmp.favor = 0; - prefix = setup_git_directory_gently(&nongit); - if (!nongit) { + if (startup_info->have_repository) { /* Read the configuration file */ git_config(git_xmerge_config, NULL); if (0 <= git_xmerge_style) diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index d8875d5892..78b9db76a0 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -3,6 +3,9 @@ #include "tag.h" #include "merge-recursive.h" +static const char builtin_merge_recursive_usage[] = + "git %s <base>... -- <head> <remote> ..."; + static const char *better_branch_name(const char *branch) { static char githead_env[8 + 40 + 1]; @@ -29,7 +32,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) o.subtree_shift = ""; if (argc < 4) - usagef("%s <base>... -- <head> <remote> ...", argv[0]); + usagef(builtin_merge_recursive_usage, argv[0]); for (i = 1; i < argc; ++i) { const char *arg = argv[i]; @@ -45,6 +48,10 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) o.subtree_shift = ""; else if (!prefixcmp(arg+2, "subtree=")) o.subtree_shift = arg + 10; + else if (!strcmp(arg+2, "renormalize")) + o.renormalize = 1; + else if (!strcmp(arg+2, "no-renormalize")) + o.renormalize = 0; else die("Unknown option %s", arg); continue; diff --git a/builtin/merge.c b/builtin/merge.c index 47e705ba9b..576e81f145 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -54,6 +54,7 @@ static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; +static int option_renormalize; static int verbosity; static int allow_rerere_auto; @@ -437,7 +438,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 0, NULL)) { + if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", sha1_to_hex(remote_head->sha1), @@ -504,6 +505,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) option_log = git_config_bool(k, v); + else if (!strcmp(k, "merge.renormalize")) + option_renormalize = git_config_bool(k, v); return git_diff_ui_config(k, v, cb); } @@ -625,6 +628,11 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; + o.renormalize = option_renormalize; + + /* + * NEEDSWORK: merge with table in builtin/merge-recursive + */ for (x = 0; x < xopts_nr; x++) { if (!strcmp(xopts[x], "ours")) o.recursive_variant = MERGE_RECURSIVE_OURS; @@ -634,6 +642,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, o.subtree_shift = ""; else if (!prefixcmp(xopts[x], "subtree=")) o.subtree_shift = xopts[x]+8; + else if (!strcmp(xopts[x], "renormalize")) + o.renormalize = 1; + else if (!strcmp(xopts[x], "no-renormalize")) + o.renormalize = 0; else die("Unknown option for merge-recursive: -X%s", xopts[x]); } @@ -818,7 +830,7 @@ static int finish_automerge(struct commit_list *common, return 0; } -static int suggest_conflicts(void) +static int suggest_conflicts(int renormalizing) { FILE *fp; int pos; @@ -1303,5 +1315,5 @@ int cmd_merge(int argc, const char **argv, const char *prefix) "stopped before committing as requested\n"); return 0; } else - return suggest_conflicts(); + return suggest_conflicts(option_renormalize); } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 06a38ac8c1..31f5c1c971 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -220,7 +220,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { - struct object_array revs = { 0, 0, NULL }; + struct object_array revs = OBJECT_ARRAY_INIT; int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { diff --git a/builtin/rerere.c b/builtin/rerere.c index 9b1e3a7cf5..642bf35587 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -1,13 +1,16 @@ #include "builtin.h" #include "cache.h" #include "dir.h" +#include "parse-options.h" #include "string-list.h" #include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" -static const char git_rerere_usage[] = -"git rerere [clear | status | diff | gc]"; +static const char * const rerere_usage[] = { + "git rerere [clear | status | diff | gc]", + NULL, +}; /* these values are days */ static int cutoff_noresolve = 15; @@ -19,6 +22,12 @@ static time_t rerere_created_at(const char *name) return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } +static time_t rerere_last_used_at(const char *name) +{ + struct stat st; + return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; +} + static void unlink_rr_item(const char *name) { unlink(rerere_path(name, "thisimage")); @@ -53,11 +62,16 @@ static void garbage_collect(struct string_list *rr) while ((e = readdir(dir))) { if (is_dot_or_dotdot(e->d_name)) continue; - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = (has_rerere_resolution(e->d_name) - ? cutoff_resolve : cutoff_noresolve); + + then = rerere_last_used_at(e->d_name); + if (then) { + cutoff = cutoff_resolve; + } else { + then = rerere_created_at(e->d_name); + if (!then) + continue; + cutoff = cutoff_noresolve; + } if (then < now - cutoff * 86400) string_list_append(&to_remove, e->d_name); } @@ -103,25 +117,26 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = STRING_LIST_INIT_DUP; - int i, fd, flags = 0; - - if (2 < argc) { - if (!strcmp(argv[1], "-h")) - usage(git_rerere_usage); - if (!strcmp(argv[1], "--rerere-autoupdate")) - flags = RERERE_AUTOUPDATE; - else if (!strcmp(argv[1], "--no-rerere-autoupdate")) - flags = RERERE_NOAUTOUPDATE; - if (flags) { - argc--; - argv++; - } - } - if (argc < 2) + int i, fd, autoupdate = -1, flags = 0; + + struct option options[] = { + OPT_SET_INT(0, "rerere-autoupdate", &autoupdate, + "register clean resolutions in index", 1), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, rerere_usage, 0); + + if (autoupdate == 1) + flags = RERERE_AUTOUPDATE; + if (autoupdate == 0) + flags = RERERE_NOAUTOUPDATE; + + if (argc < 1) return rerere(flags); - if (!strcmp(argv[1], "forget")) { - const char **pathspec = get_pathspec(prefix, argv + 2); + if (!strcmp(argv[0], "forget")) { + const char **pathspec = get_pathspec(prefix, argv + 1); return rerere_forget(pathspec); } @@ -129,26 +144,26 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (fd < 0) return 0; - if (!strcmp(argv[1], "clear")) { + if (!strcmp(argv[0], "clear")) { for (i = 0; i < merge_rr.nr; i++) { const char *name = (const char *)merge_rr.items[i].util; if (!has_rerere_resolution(name)) unlink_rr_item(name); } unlink_or_warn(git_path("MERGE_RR")); - } else if (!strcmp(argv[1], "gc")) + } else if (!strcmp(argv[0], "gc")) garbage_collect(&merge_rr); - else if (!strcmp(argv[1], "status")) + else if (!strcmp(argv[0], "status")) for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); - else if (!strcmp(argv[1], "diff")) + else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; diff_two(rerere_path(name, "preimage"), path, path, path); } else - usage(git_rerere_usage); + usage_with_options(rerere_usage, options); string_list_clear(&merge_rr, 1); return 0; diff --git a/builtin/reset.c b/builtin/reset.c index 1283068fd2..0037be4693 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -318,7 +318,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * affecting the working tree nor HEAD. */ if (i < argc) { if (reset_type == MIXED) - warning("--mixed option is deprecated with paths."); + warning("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."); else if (reset_type != NONE) die("Cannot do %s reset with paths.", reset_type_names[reset_type]); diff --git a/builtin/revert.c b/builtin/revert.c index 9215e66504..4b47ace36b 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -231,27 +231,30 @@ static void set_author_ident_env(const char *message) sha1_to_hex(commit->object.sha1)); } -static char *help_msg(void) +static void advise(const char *advice, ...) { - struct strbuf helpbuf = STRBUF_INIT; - char *msg = getenv("GIT_CHERRY_PICK_HELP"); + va_list params; - if (msg) - return msg; + va_start(params, advice); + vreportf("hint: ", advice, params); + va_end(params); +} - strbuf_addstr(&helpbuf, " After resolving the conflicts,\n" - "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n" - "and commit the result"); +static void print_advice(void) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); - if (action == CHERRY_PICK) { - strbuf_addf(&helpbuf, " with: \n" - "\n" - " git commit -c %s\n", - sha1_to_hex(commit->object.sha1)); + if (msg) { + fprintf(stderr, "%s\n", msg); + return; } - else - strbuf_addch(&helpbuf, '.'); - return strbuf_detach(&helpbuf, NULL); + + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + + if (action == CHERRY_PICK) + advise("and commit the result with 'git commit -c %s'", + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -301,10 +304,9 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) return write_ref_sha1(ref_lock, to, "cherry-pick"); } -static void do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf, - char *defmsg) +static int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf) { struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; @@ -314,6 +316,13 @@ static void do_recursive_merge(struct commit *base, struct commit *next, index_fd = hold_locked_index(&index_lock, 1); read_cache(); + + /* + * NEEDSWORK: cherry-picking between branches with + * different end-of-line normalization is a pain; + * plumb in an option to set o.renormalize? + * (or better: arbitrary -X options) + */ init_merge_options(&o); o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; @@ -347,14 +356,35 @@ static void do_recursive_merge(struct commit *base, struct commit *next, i++; } } - write_message(msgbuf, defmsg); - fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg()); - rerere(allow_rerere_auto); - exit(1); } - write_message(msgbuf, defmsg); - fprintf(stderr, "Finished one %s.\n", me); + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +static int run_git_commit(const char *defmsg) +{ + /* 6 is max possible length of our args array including NULL */ + const char *args[6]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (signoff) + args[i++] = "-s"; + if (!edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); } static int do_pick_commit(void) @@ -365,6 +395,7 @@ static int do_pick_commit(void) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; + int res; if (no_commit) { /* @@ -460,63 +491,40 @@ static int do_pick_commit(void) } } - if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) - do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf, defmsg); - else { - int res; + if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf); + write_message(&msgbuf, defmsg); + } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; + write_message(&msgbuf, defmsg); + commit_list_insert(base, &common); commit_list_insert(next, &remotes); res = try_merge_command(strategy, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); - if (res) { - fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", - me, strategy, help_msg()); - rerere(allow_rerere_auto); - exit(1); - } } - free_message(&msg); - - /* - * - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ - - if (!no_commit) { - /* 6 is max possible length of our args array including NULL */ - const char *args[6]; - int res; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (signoff) - args[i++] = "-s"; - if (!edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - res = run_command_v_opt(args, RUN_GIT_CMD); - free(defmsg); - - return res; + if (res) { + error("could not %s %s... %s", + action == REVERT ? "revert" : "apply", + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(); + rerere(allow_rerere_auto); + } else { + if (!no_commit) + res = run_git_commit(defmsg); } + free_message(&msg); free(defmsg); - return 0; + return res; } static void prepare_revs(struct rev_info *revs) diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 0a9681ba7e..2135b0dde1 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -249,7 +249,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) { static struct shortlog log; static struct rev_info rev; - int nongit; + int nongit = !startup_info->have_repository; static const struct option options[] = { OPT_BOOLEAN('n', "numbered", &log.sort_by_number, @@ -265,7 +265,6 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct parse_opt_ctx_t ctx; - prefix = setup_git_directory_gently(&nongit); git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); diff --git a/builtin/var.c b/builtin/var.c index 70fdb4dec7..0744bb8318 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -74,14 +74,9 @@ static int show_config(const char *var, const char *value, void *cb) int cmd_var(int argc, const char **argv, const char *prefix) { - const char *val; - int nongit; - if (argc != 2) { + const char *val = NULL; + if (argc != 2) usage(var_usage); - } - - setup_git_directory_gently(&nongit); - val = NULL; if (strcmp(argv[1], "-l") == 0) { git_config(show_config, NULL); @@ -1057,6 +1057,7 @@ extern void trace_argv_printf(const char **argv, const char *format, ...); extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst, enum safe_crlf checksafe); extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); +extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst); /* add */ /* @@ -1102,6 +1103,12 @@ int split_cmdline(char *cmdline, const char ***argv); /* Takes a negative value returned by split_cmdline */ const char *split_cmdline_strerror(int cmdline_errno); +/* git.c */ +struct startup_info { + int have_repository; +}; +extern struct startup_info *startup_info; + /* builtin/merge.c */ int checkout_fast_forward(const unsigned char *from, const unsigned char *to); diff --git a/compat/mingw.c b/compat/mingw.c index 96be8a02cf..f2d9e1fd97 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -304,8 +304,13 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) goto revert_attrs; } - time_t_to_filetime(times->modtime, &mft); - time_t_to_filetime(times->actime, &aft); + if (times) { + time_t_to_filetime(times->modtime, &mft); + time_t_to_filetime(times->actime, &aft); + } else { + GetSystemTimeAsFileTime(&mft); + aft = mft; + } if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) { errno = EINVAL; rc = -1; diff --git a/compat/strtok_r.c b/compat/strtok_r.c new file mode 100644 index 0000000000..7b5d568a96 --- /dev/null +++ b/compat/strtok_r.c @@ -0,0 +1,61 @@ +/* Reentrant string tokenizer. Generic version. + Copyright (C) 1991,1996-1999,2001,2004 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., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include "../git-compat-util.h" + +/* Parse S into tokens separated by characters in DELIM. + If S is NULL, the saved pointer in SAVE_PTR is used as + the next starting point. For example: + char s[] = "-abc-=-def"; + char *sp; + x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def" + x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL + x = strtok_r(NULL, "=", &sp); // x = NULL + // s = "abc\0-def\0" +*/ +char * +gitstrtok_r (char *s, const char *delim, char **save_ptr) +{ + char *token; + + if (s == NULL) + s = *save_ptr; + + /* Scan leading delimiters. */ + s += strspn (s, delim); + if (*s == '\0') + { + *save_ptr = s; + return NULL; + } + + /* Find the end of the token. */ + token = s; + s = strpbrk (token, delim); + if (s == NULL) + /* This token finishes the string. */ + *save_ptr = token + strlen (token); + else + { + /* Terminate the token and make *SAVE_PTR point past it. */ + *s = '\0'; + *save_ptr = s + 1; + } + return token; +} diff --git a/config.mak.in b/config.mak.in index b4e65c32b2..4ffd77420f 100644 --- a/config.mak.in +++ b/config.mak.in @@ -46,6 +46,7 @@ NO_IPV6=@NO_IPV6@ NO_C99_FORMAT=@NO_C99_FORMAT@ NO_HSTRERROR=@NO_HSTRERROR@ NO_STRCASESTR=@NO_STRCASESTR@ +NO_STRTOK_R=@NO_STRTOK_R@ NO_MEMMEM=@NO_MEMMEM@ NO_STRLCPY=@NO_STRLCPY@ NO_UINTMAX_T=@NO_UINTMAX_T@ diff --git a/configure.ac b/configure.ac index 5601e8bac9..708e7b86ce 100644 --- a/configure.ac +++ b/configure.ac @@ -783,6 +783,12 @@ GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=YesPlease]) AC_SUBST(NO_STRCASESTR) # +# Define NO_STRTOK_R if you don't have strtok_r +GIT_CHECK_FUNC(strtok_r, +[NO_STRTOK_R=], +[NO_STRTOK_R=YesPlease]) +AC_SUBST(NO_STRTOK_R) +# # Define NO_MEMMEM if you don't have memmem. GIT_CHECK_FUNC(memmem, [NO_MEMMEM=], diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index 8f617fcb70..7b922c3948 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -15,7 +15,10 @@ log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) ff allow fast-forward (default) +ff-only abort if fast-forward is not possible +rerere-autoupdate update index with any reused conflict resolution s,strategy= merge strategy to use +X= option for selected merge strategy m,message= message to be used for the merge commit (if any) " @@ -25,26 +28,32 @@ require_work_tree cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in the middle of a conflicted merge." + die "Merge is not possible because you have unmerged files." + +! test -e "$GIT_DIR/MERGE_HEAD" || + die 'You have not concluded your merge (MERGE_HEAD exists).' LF=' ' all_strategies='recur recursive octopus resolve stupid ours subtree' all_strategies="$all_strategies recursive-ours recursive-theirs" +not_strategies='base file index tree' default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_fast_forward_strategies='subtree ours' no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs' use_strategies= +xopt= allow_fast_forward=t +fast_forward_only= allow_trivial_merge=t -squash= no_commit= log_arg= +squash= no_commit= log_arg= rr_arg= dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ - "$GIT_DIR/MERGE_STASH" || exit 1 + "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1 } savestate() { @@ -131,21 +140,34 @@ finish () { merge_name () { remote="$1" rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return - bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) - if test "$rh" = "$bh" - then - echo "$rh branch '$remote' of ." - elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') && + if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') && git show-ref -q --verify "refs/heads/$truname" 2>/dev/null then echo "$rh branch '$truname' (early part) of ." - elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD" + return + fi + if found_ref=$(git rev-parse --symbolic-full-name --verify \ + "$remote" 2>/dev/null) + then + expanded=$(git check-ref-format --branch "$remote") || + exit + if test "${found_ref#refs/heads/}" != "$found_ref" + then + echo "$rh branch '$expanded' of ." + return + elif test "${found_ref#refs/remotes/}" != "$found_ref" + then + echo "$rh remote branch '$expanded' of ." + return + fi + fi + if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD" then sed -e 's/ not-for-merge / /' -e 1q \ "$GIT_DIR/FETCH_HEAD" - else - echo "$rh commit '$remote'" + return fi + echo "$rh commit '$remote'" } parse_config () { @@ -172,16 +194,36 @@ parse_config () { --no-ff) test "$squash" != t || die "You cannot combine --squash with --no-ff." + test "$fast_forward_only" != t || + die "You cannot combine --ff-only with --no-ff." allow_fast_forward=f ;; + --ff-only) + test "$allow_fast_forward" != f || + die "You cannot combine --ff-only with --no-ff." + fast_forward_only=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + rr_arg=$1 ;; -s|--strategy) shift case " $all_strategies " in *" $1 "*) - use_strategies="$use_strategies$1 " ;; + use_strategies="$use_strategies$1 " + ;; *) - die "available strategies are: $all_strategies" ;; + case " $not_strategies " in + *" $1 "*) + false + esac && + type "git-merge-$1" >/dev/null 2>&1 || + die "available strategies are: $all_strategies" + use_strategies="$use_strategies$1 " + ;; esac ;; + -X) + shift + xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")" + ;; -m|--message) shift merge_msg="$1" @@ -245,6 +287,10 @@ then exit 1 fi + test "$squash" != t || + die "Squash commit into empty head not supported yet" + test "$allow_fast_forward" = t || + die "Non-fast-forward into an empty head does not make sense" rh=$(git rev-parse --verify "$1^0") || die "$1 - not something we can merge" @@ -261,12 +307,18 @@ else # the given message. If remote is invalid we will die # later in the common codepath so we discard the error # in this loop. - merge_name=$(for remote + merge_msg="$( + for remote do merge_name "$remote" - done | git fmt-merge-msg $log_arg - ) - merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" + done | + if test "$have_message" = t + then + git fmt-merge-msg -m "$merge_msg" $log_arg + else + git fmt-merge-msg $log_arg + fi + )" fi head=$(git rev-parse --verify "$head_arg"^0) || usage @@ -335,7 +387,7 @@ case "$#" in common=$(git merge-base --all $head "$@") ;; *) - common=$(git show-branch --merge-base $head "$@") + common=$(git merge-base --all --octopus $head "$@") ;; esac echo "$head" >"$GIT_DIR/ORIG_HEAD" @@ -373,8 +425,8 @@ t,1,"$head",*) # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh 2>/dev/null - case "$allow_trivial_merge" in - t) + case "$allow_trivial_merge,$fast_forward_only" in + t,) # See if it is really trivial. git var GIT_COMMITTER_IDENT >/dev/null || exit echo "Trying really trivial in-index merge..." @@ -413,6 +465,11 @@ t,1,"$head",*) ;; esac +if test "$fast_forward_only" = t +then + die "Not possible to fast-forward, aborting." +fi + # We are going to make a new commit. git var GIT_COMMITTER_IDENT >/dev/null || exit @@ -451,7 +508,7 @@ do # Remember which strategy left the state in the working tree wt_strategy=$strategy - git-merge-$strategy $common -- "$head_arg" "$@" + eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"' exit=$? if test "$no_commit" = t && test "$exit" = 0 then @@ -489,9 +546,9 @@ if test '' != "$result_tree" then if test "$allow_fast_forward" = "t" then - parents=$(git show-branch --independent "$head" "$@") + parents=$(git merge-base --independent "$head" "$@") else - parents=$(git rev-parse "$head" "$@") + parents=$(git rev-parse "$head" "$@") fi parents=$(echo "$parents" | sed -e 's/^/-p /') result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit @@ -533,7 +590,15 @@ else do echo $remote done >"$GIT_DIR/MERGE_HEAD" - printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" + printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" || + die "Could not write to $GIT_DIR/MERGE_MSG" + if test "$allow_fast_forward" != t + then + printf "%s" no-ff + else + : + fi >"$GIT_DIR/MERGE_MODE" || + die "Could not write to $GIT_DIR/MERGE_MODE" fi if test "$merge_was_ok" = t @@ -550,6 +615,6 @@ Conflicts: sed -e 's/^[^ ]* / /' | uniq } >>"$GIT_DIR/MERGE_MSG" - git rerere + git rerere $rr_arg die "Automatic merge failed; fix conflicts and then commit the result." fi diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh index 49f00321b2..60a05a8b97 100755 --- a/contrib/examples/git-revert.sh +++ b/contrib/examples/git-revert.sh @@ -181,7 +181,6 @@ Conflicts: esac exit 1 } -echo >&2 "Finished one $me." # If we are cherry-pick, and if the merge did not result in # hand-editing, we will hit this commit and inherit the original diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c index e9b9ba4da4..a2677b03e0 100644 --- a/contrib/svn-fe/svn-fe.c +++ b/contrib/svn-fe/svn-fe.c @@ -10,6 +10,7 @@ int main(int argc, char **argv) { svndump_init(NULL); svndump_read((argc > 1) ? argv[1] : NULL); + svndump_deinit(); svndump_reset(); return 0; } diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt index de30f83a1f..35f84bd9e7 100644 --- a/contrib/svn-fe/svn-fe.txt +++ b/contrib/svn-fe/svn-fe.txt @@ -12,7 +12,7 @@ svnadmin dump --incremental REPO | svn-fe [url] | git fast-import DESCRIPTION ----------- -Converts a Subversion dumpfile (version: 2) into input suitable for +Converts a Subversion dumpfile into input suitable for git-fast-import(1) and similar importers. REPO is a path to a Subversion repository mirrored on the local disk. Remote Subversion repositories can be mirrored on local disk using the `svnsync` @@ -25,6 +25,9 @@ Subversion's repository dump format is documented in full in Files in this format can be generated using the 'svnadmin dump' or 'svk admin dump' command. +Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3) +are not supported. + OUTPUT FORMAT ------------- The fast-import format is documented by the git-fast-import(1) @@ -43,11 +46,9 @@ user <user@UUID> as committer, where 'user' is the value of the `svn:author` property and 'UUID' the repository's identifier. -To support incremental imports, 'svn-fe' will put a `git-svn-id` -line at the end of each commit log message if passed an url on the -command line. This line has the form `git-svn-id: URL@REVNO UUID`. - -Empty directories and unknown properties are silently discarded. +To support incremental imports, 'svn-fe' puts a `git-svn-id` line at +the end of each commit log message if passed an url on the command +line. This line has the form `git-svn-id: URL@REVNO UUID`. The resulting repository will generally require further processing to put each project in its own repository and to separate the history @@ -56,9 +57,9 @@ may be useful for this purpose. BUGS ---- -Litters the current working directory with .bin files for -persistence. Will be fixed when the svn-fe infrastructure is aware of -a Git working directory. +Empty directories and unknown properties are silently discarded. + +The exit status does not reflect whether an error was detected. SEE ALSO -------- @@ -93,7 +93,8 @@ static int is_binary(unsigned long size, struct text_stat *stats) return 0; } -static enum eol determine_output_conversion(enum action action) { +static enum eol determine_output_conversion(enum action action) +{ switch (action) { case CRLF_BINARY: return EOL_UNSET; @@ -693,7 +694,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check) return !!ATTR_TRUE(value); } -enum action determine_action(enum action text_attr, enum eol eol_attr) { +static enum action determine_action(enum action text_attr, enum eol eol_attr) +{ if (text_attr == CRLF_BINARY) return CRLF_BINARY; if (eol_attr == EOL_LF) @@ -739,7 +741,9 @@ int convert_to_git(const char *path, const char *src, size_t len, return ret | ident_to_git(path, src, len, dst, ident); } -int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst) +static int convert_to_working_tree_internal(const char *path, const char *src, + size_t len, struct strbuf *dst, + int normalizing) { struct git_attr_check check[5]; enum action action = CRLF_GUESS; @@ -765,11 +769,32 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc src = dst->buf; len = dst->len; } - action = determine_action(action, eol_attr); - ret |= crlf_to_worktree(path, src, len, dst, action); + /* + * CRLF conversion can be skipped if normalizing, unless there + * is a smudge filter. The filter might expect CRLFs. + */ + if (filter || !normalizing) { + action = determine_action(action, eol_attr); + ret |= crlf_to_worktree(path, src, len, dst, action); + if (ret) { + src = dst->buf; + len = dst->len; + } + } + return ret | apply_filter(path, src, len, dst, filter); +} + +int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst) +{ + return convert_to_working_tree_internal(path, src, len, dst, 0); +} + +int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst) +{ + int ret = convert_to_working_tree_internal(path, src, len, dst, 1); if (ret) { src = dst->buf; len = dst->len; } - return ret | apply_filter(path, src, len, dst, filter); + return ret | convert_to_git(path, src, len, dst, 0); } diff --git a/diff-delta.c b/diff-delta.c index 464ac3ffc0..93385e12ba 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -146,7 +146,14 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) /* Determine index hash size. Note that indexing skips the first byte to allow for optimizing the Rabin's polynomial initialization in create_delta(). */ - entries = (bufsize - 1) / RABIN_WINDOW; + entries = (bufsize - 1) / RABIN_WINDOW; + if (bufsize >= 0xffffffffUL) { + /* + * Current delta format can't encode offsets into + * reference buffer with more than 32 bits. + */ + entries = 0xfffffffeU / RABIN_WINDOW; + } hsize = entries / 4; for (i = 4; (1u << i) < hsize && i < 31; i++); hsize = 1 << i; @@ -3853,6 +3853,13 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) len2, p->two->path); git_SHA1_Update(&ctx, buffer, len1); + if (diff_filespec_is_binary(p->one) || + diff_filespec_is_binary(p->two)) { + git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40); + git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40); + continue; + } + xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; diff --git a/environment.c b/environment.c index 83d38d3c23..eeb26876a1 100644 --- a/environment.c +++ b/environment.c @@ -53,6 +53,7 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; char *notes_ref_name; int grafts_replace_parents = 1; int core_apply_sparse_checkout; +struct startup_info *startup_info; /* Parallel index stat data preload? */ int core_preload_index = 0; diff --git a/fast-import.c b/fast-import.c index dd51ac48b6..2317b0fe75 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1528,6 +1528,14 @@ static int tree_content_remove( for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (slash1 && !S_ISDIR(e->versions[1].mode)) + /* + * If p names a file in some subdirectory, and a + * file or symlink matching the name of the + * parent directory of p exists, then p cannot + * exist and need not be deleted. + */ + return 1; if (!slash1 || !S_ISDIR(e->versions[1].mode)) goto del_entry; if (!e->tree) diff --git a/git-compat-util.h b/git-compat-util.h index fe845ae639..877096ecb0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -312,6 +312,11 @@ extern size_t gitstrlcpy(char *, const char *, size_t); extern uintmax_t gitstrtoumax(const char *, char **, int); #endif +#ifdef NO_STRTOK_R +#define strtok_r gitstrtok_r +extern char *gitstrtok_r(char *s, const char *delim, char **save_ptr); +#endif + #ifdef NO_HSTRERROR #define hstrerror githstrerror extern const char *githstrerror(int herror); diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 3419247d03..eb2dff55f8 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -111,11 +111,12 @@ VERBOSE= OK_TO_SKIP_PRE_REBASE= REBASE_ROOT= AUTOSQUASH= +test "$(git config --bool rebase.autosquash)" = "true" && AUTOSQUASH=t NEVER_FF= -GIT_CHERRY_PICK_HELP=" After resolving the conflicts, -mark the corrected paths with 'git add <paths>', and -run 'git rebase --continue'" +GIT_CHERRY_PICK_HELP="\ +hint: after resolving the conflicts, mark the corrected paths +hint: with 'git add <paths>' and run 'git rebase --continue'" export GIT_CHERRY_PICK_HELP warn () { @@ -831,6 +832,9 @@ first and then run 'git rebase --continue' again." --autosquash) AUTOSQUASH=t ;; + --no-autosquash) + AUTOSQUASH= + ;; --onto) shift ONTO=$(parse_onto "$1") || diff --git a/git-submodule.sh b/git-submodule.sh index 170186f494..9ebbab798d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -839,10 +839,11 @@ cmd_sync() if test -e "$path"/.git then ( + say "Synchronizing submodule url for '$name'" + git config submodule."$name".url "$url" clear_local_git_env cd "$path" remote=$(get_default_remote) - say "Synchronizing submodule url for '$name'" git config remote."$remote".url "$url" ) fi @@ -14,6 +14,7 @@ const char git_usage_string[] = const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; +static struct startup_info git_startup_info; static int use_pager = -1; struct pager_config { const char *cmd; @@ -230,13 +231,14 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; -#define RUN_SETUP (1<<0) -#define USE_PAGER (1<<1) +#define RUN_SETUP (1<<0) +#define RUN_SETUP_GENTLY (1<<1) +#define USE_PAGER (1<<2) /* * require working tree to be present -- anything uses this needs * RUN_SETUP for reading from the configuration file. */ -#define NEED_WORK_TREE (1<<2) +#define NEED_WORK_TREE (1<<3) struct cmd_struct { const char *cmd; @@ -255,8 +257,12 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) if (!help) { if (p->option & RUN_SETUP) prefix = setup_git_directory(); + if (p->option & RUN_SETUP_GENTLY) { + int nongit_ok; + prefix = setup_git_directory_gently(&nongit_ok); + } - if (use_pager == -1 && p->option & RUN_SETUP) + if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) use_pager = check_pager_config(p->cmd); if (use_pager == -1 && p->option & USE_PAGER) use_pager = 1; @@ -296,12 +302,12 @@ static void handle_internal_command(int argc, const char **argv) { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP }, - { "apply", cmd_apply }, + { "apply", cmd_apply, RUN_SETUP_GENTLY }, { "archive", cmd_archive }, { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE }, { "blame", cmd_blame, RUN_SETUP }, { "branch", cmd_branch, RUN_SETUP }, - { "bundle", cmd_bundle }, + { "bundle", cmd_bundle, RUN_SETUP_GENTLY }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, { "checkout-index", cmd_checkout_index, @@ -314,7 +320,7 @@ static void handle_internal_command(int argc, const char **argv) { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, - { "config", cmd_config }, + { "config", cmd_config, RUN_SETUP_GENTLY }, { "count-objects", cmd_count_objects, RUN_SETUP }, { "describe", cmd_describe, RUN_SETUP }, { "diff", cmd_diff }, @@ -331,21 +337,21 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep }, + { "grep", cmd_grep, RUN_SETUP_GENTLY }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, - { "index-pack", cmd_index_pack }, + { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, - { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, - { "ls-remote", cmd_ls_remote }, + { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, - { "merge-file", cmd_merge_file }, + { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY }, { "merge-index", cmd_merge_index, RUN_SETUP }, { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, @@ -361,7 +367,7 @@ static void handle_internal_command(int argc, const char **argv) { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, { "patch-id", cmd_patch_id }, - { "peek-remote", cmd_ls_remote }, + { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, { "pickaxe", cmd_blame, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, @@ -371,7 +377,7 @@ static void handle_internal_command(int argc, const char **argv) { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, { "replace", cmd_replace, RUN_SETUP }, - { "repo-config", cmd_config }, + { "repo-config", cmd_config, RUN_SETUP_GENTLY }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, @@ -379,9 +385,9 @@ static void handle_internal_command(int argc, const char **argv) { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, - { "shortlog", cmd_shortlog, USE_PAGER }, + { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, - { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "show", cmd_show, RUN_SETUP }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, @@ -393,10 +399,10 @@ static void handle_internal_command(int argc, const char **argv) { "update-ref", cmd_update_ref, RUN_SETUP }, { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, - { "var", cmd_var }, + { "var", cmd_var, RUN_SETUP_GENTLY }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, - { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "whatchanged", cmd_whatchanged, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, { "show-ref", cmd_show_ref, RUN_SETUP }, @@ -490,6 +496,8 @@ int main(int argc, const char **argv) { const char *cmd; + startup_info = &git_startup_info; + cmd = git_extract_argv0_path(argv[0]); if (!cmd) cmd = "git-help"; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d0687f4581..a85e2f6319 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5191,15 +5191,15 @@ sub git_summary { } sub git_tag { - my $head = git_get_head_hash($project); - git_header_html(); - git_print_page_nav('','', $head,undef,$head); my %tag = parse_tag($hash); if (! %tag) { die_error(404, "Unknown tag object"); } + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . "<table class=\"object_header\">\n" . diff --git a/ll-merge.c b/ll-merge.c index 3764a1ab72..6bb3095c3a 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -46,7 +46,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, * or common ancestor for an internal merge. Still return * "conflicted merge" status. */ - mmfile_t *stolen = (flag & 01) ? orig : src1; + mmfile_t *stolen = (flag & LL_OPT_VIRTUAL_ANCESTOR) ? orig : src1; result->ptr = stolen->ptr; result->size = stolen->size; @@ -79,7 +79,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, memset(&xmp, 0, sizeof(xmp)); xmp.level = XDL_MERGE_ZEALOUS; - xmp.favor= (flag >> 1) & 03; + xmp.favor = ll_opt_favor(flag); if (git_xmerge_style >= 0) xmp.style = git_xmerge_style; if (marker_size > 0) @@ -99,7 +99,8 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, int flag, int marker_size) { /* Use union favor */ - flag = (flag & 1) | (XDL_MERGE_FAVOR_UNION << 1); + flag &= ~LL_OPT_FAVOR_MASK; + flag |= create_ll_flag(XDL_MERGE_FAVOR_UNION); return ll_xdl_merge(drv_unused, result, path_unused, orig, NULL, src1, NULL, src2, NULL, flag, marker_size); @@ -321,6 +322,16 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2] return git_checkattr(path, 2, check); } +static void normalize_file(mmfile_t *mm, const char *path) +{ + struct strbuf strbuf = STRBUF_INIT; + if (renormalize_buffer(path, mm->ptr, mm->size, &strbuf)) { + free(mm->ptr); + mm->size = strbuf.len; + mm->ptr = strbuf_detach(&strbuf, NULL); + } +} + int ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, @@ -332,8 +343,13 @@ int ll_merge(mmbuffer_t *result_buf, const char *ll_driver_name = NULL; int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; const struct ll_merge_driver *driver; - int virtual_ancestor = flag & 01; + int virtual_ancestor = flag & LL_OPT_VIRTUAL_ANCESTOR; + if (flag & LL_OPT_RENORMALIZE) { + normalize_file(ancestor, path); + normalize_file(ours, path); + normalize_file(theirs, path); + } if (!git_path_check_merge(path, check)) { ll_driver_name = check[0].value; if (check[1].value) { diff --git a/ll-merge.h b/ll-merge.h index 57754cc8ca..ff7ca87bfa 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -5,6 +5,21 @@ #ifndef LL_MERGE_H #define LL_MERGE_H +#define LL_OPT_VIRTUAL_ANCESTOR (1 << 0) +#define LL_OPT_FAVOR_MASK ((1 << 1) | (1 << 2)) +#define LL_OPT_FAVOR_SHIFT 1 +#define LL_OPT_RENORMALIZE (1 << 3) + +static inline int ll_opt_favor(int flag) +{ + return (flag & LL_OPT_FAVOR_MASK) >> LL_OPT_FAVOR_SHIFT; +} + +static inline int create_ll_flag(int favor) +{ + return ((favor << LL_OPT_FAVOR_SHIFT) & LL_OPT_FAVOR_MASK); +} + int ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, diff --git a/merge-recursive.c b/merge-recursive.c index 638076ec6e..aadd48c4fc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -644,7 +644,9 @@ static int merge_3way(struct merge_options *o, merge_status = ll_merge(result_buf, a->path, &orig, base_name, &src1, name1, &src2, name2, - (!!o->call_depth) | (favor << 1)); + ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) | + (o->renormalize ? LL_OPT_RENORMALIZE : 0) | + create_ll_flag(favor))); free(name1); free(name2); @@ -1017,14 +1019,22 @@ static int process_renames(struct merge_options *o, if (mfi.clean && sha_eq(mfi.sha, ren1->pair->two->sha1) && - mfi.mode == ren1->pair->two->mode) + mfi.mode == ren1->pair->two->mode) { /* - * This messaged is part of + * This message is part of * t6022 test. If you change * it update the test too. */ output(o, 3, "Skipped %s (merged same as existing)", ren1_dst); - else { + + /* There may be higher stage entries left + * in the index (e.g. due to a D/F + * conflict) that need to be resolved. + */ + if (!ren1->dst_entry->stages[2].mode != + !ren1->dst_entry->stages[3].mode) + ren1->dst_entry->processed = 0; + } else { if (mfi.merge || !mfi.clean) output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) @@ -1054,6 +1064,53 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; } +static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst) +{ + void *buf; + enum object_type type; + unsigned long size; + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("cannot read object %s", sha1_to_hex(sha1)); + if (type != OBJ_BLOB) { + free(buf); + return error("object %s is not a blob", sha1_to_hex(sha1)); + } + strbuf_attach(dst, buf, size, size + 1); + return 0; +} + +static int blob_unchanged(const unsigned char *o_sha, + const unsigned char *a_sha, + int renormalize, const char *path) +{ + struct strbuf o = STRBUF_INIT; + struct strbuf a = STRBUF_INIT; + int ret = 0; /* assume changed for safety */ + + if (sha_eq(o_sha, a_sha)) + return 1; + if (!renormalize) + return 0; + + assert(o_sha && a_sha); + if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a)) + goto error_return; + /* + * Note: binary | is used so that both renormalizations are + * performed. Comparison can be skipped if both files are + * unchanged since their sha1s have already been compared. + */ + if (renormalize_buffer(path, o.buf, o.len, &o) | + renormalize_buffer(path, a.buf, o.len, &a)) + ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len)); + +error_return: + strbuf_release(&o); + strbuf_release(&a); + return ret; +} + /* Per entry merge function */ static int process_entry(struct merge_options *o, const char *path, struct stage_data *entry) @@ -1063,6 +1120,7 @@ static int process_entry(struct merge_options *o, print_index_entry("\tpath: ", entry); */ int clean_merge = 1; + int normalize = o->renormalize; unsigned o_mode = entry->stages[1].mode; unsigned a_mode = entry->stages[2].mode; unsigned b_mode = entry->stages[3].mode; @@ -1070,11 +1128,12 @@ 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); + entry->processed = 1; if (o_sha && (!a_sha || !b_sha)) { /* Case A: Deleted in one */ if ((!a_sha && !b_sha) || - (sha_eq(a_sha, o_sha) && !b_sha) || - (!a_sha && sha_eq(b_sha, o_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) @@ -1102,33 +1161,28 @@ static int process_entry(struct merge_options *o, } else if ((!o_sha && a_sha && !b_sha) || (!o_sha && !a_sha && b_sha)) { /* Case B: Added in one. */ - const char *add_branch; - const char *other_branch; unsigned mode; const unsigned char *sha; - const char *conf; if (a_sha) { - add_branch = o->branch1; - other_branch = o->branch2; mode = a_mode; sha = a_sha; - conf = "file/directory"; } else { - add_branch = o->branch2; - other_branch = o->branch1; mode = b_mode; sha = b_sha; - conf = "directory/file"; } if (string_list_has_string(&o->current_directory_set, path)) { - const 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); - remove_file(o, 0, path, 0); - update_file(o, 0, sha, mode, new_path); + /* Handle D->F conflicts after all subfiles */ + entry->processed = 0; + /* But get any file out of the way now, so conflicted + * entries below the directory of the same name can + * be put in the working directory. + */ + if (a_sha) + output(o, 2, "Removing %s", path); + /* do not touch working file if it did not exist */ + remove_file(o, 0, path, !a_sha); + return 1; /* Assume clean till processed */ } else { output(o, 2, "Adding %s", path); update_file(o, 1, sha, mode, path); @@ -1176,6 +1230,64 @@ static int process_entry(struct merge_options *o, return clean_merge; } +/* + * Per entry merge function for D/F conflicts, to be called only after + * all files below dir have been processed. We do this because in the + * cases we can cleanly resolve D/F conflicts, process_entry() can clean + * out all the files below the directory for us. + */ +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); + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + struct stat st; + + /* We currently only handle D->F cases */ + assert((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)); + + entry->processed = 1; + + if (a_sha) { + add_branch = o->branch1; + other_branch = o->branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = o->branch2; + other_branch = o->branch1; + mode = b_mode; + 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); + 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); + remove_file(o, 0, path, 0); + update_file(o, 0, sha, mode, new_path); + } else { + output(o, 2, "Adding %s", path); + update_file(o, 1, sha, mode, path); + } + + return clean_merge; +} + void set_porcelain_error_msgs(const char **msgs, const char *cmd) { const char *msg; @@ -1269,6 +1381,13 @@ int merge_trees(struct merge_options *o, && !process_entry(o, path, e)) 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; + } string_list_clear(re_merge, 0); string_list_clear(re_head, 0); @@ -1456,6 +1575,7 @@ void init_merge_options(struct merge_options *o) o->buffer_output = 1; o->diff_rename_limit = -1; o->merge_rename_limit = -1; + o->renormalize = 0; git_config(merge_recursive_config, o); if (getenv("GIT_MERGE_VERBOSITY")) o->verbosity = diff --git a/merge-recursive.h b/merge-recursive.h index 08f9850367..196f053106 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -14,6 +14,7 @@ struct merge_options { } recursive_variant; const char *subtree_shift; unsigned buffer_output : 1; + unsigned renormalize : 1; int verbosity; int diff_rename_limit; int merge_rename_limit; @@ -21,6 +21,8 @@ struct object_array { } *objects; }; +#define OBJECT_ARRAY_INIT { 0, 0, NULL } + #define TYPE_BITS 3 #define FLAG_BITS 27 diff --git a/parse-options.h b/parse-options.h index 7435cdbf1d..d982f0f1bf 100644 --- a/parse-options.h +++ b/parse-options.h @@ -69,7 +69,7 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * `flags`:: * mask of parse_opt_option_flags. * PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs) - * PARSE_OPT_NOARG: says that this option takes no argument + * PARSE_OPT_NOARG: says that this option does not take an argument * PARSE_OPT_NONEG: says that this option cannot be negated * PARSE_OPT_HIDDEN: this option is skipped in the default usage, and * shown only in the full usage. diff --git a/reachable.c b/reachable.c index b515fa2de3..a03fabf060 100644 --- a/reachable.c +++ b/reachable.c @@ -90,7 +90,7 @@ static void walk_commit_list(struct rev_info *revs) { int i; struct commit *commit; - struct object_array objects = { 0, 0, NULL }; + struct object_array objects = OBJECT_ARRAY_INIT; /* Walk all commits, process their trees */ while ((commit = get_revision(revs)) != NULL) @@ -319,6 +319,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu if (!mmfile[i].ptr && !mmfile[i].size) mmfile[i].ptr = xstrdup(""); } + /* + * NEEDSWORK: handle conflicts from merges with + * merge.renormalize set, too + */ ll_merge(&result, path, &mmfile[0], NULL, &mmfile[1], "ours", &mmfile[2], "theirs", 0); @@ -378,7 +382,13 @@ static int merge(const char *name, const char *path) } ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0); if (!ret) { - FILE *f = fopen(path, "w"); + FILE *f; + + if (utime(rerere_path(name, "postimage"), NULL) < 0) + warning("failed utime() on %s: %s", + rerere_path(name, "postimage"), + strerror(errno)); + f = fopen(path, "w"); if (!f) return error("Could not open %s: %s", path, strerror(errno)); @@ -313,21 +313,129 @@ const char *read_gitfile_gently(const char *path) return path; } +static const char *setup_explicit_git_dir(const char *gitdirenv, + const char *work_tree_env, int *nongit_ok) +{ + static char buffer[1024 + 1]; + const char *retval; + + if (PATH_MAX - 40 < strlen(gitdirenv)) + die("'$%s' too big", GIT_DIR_ENVIRONMENT); + if (!is_git_directory(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } + die("Not a git repository: '%s'", gitdirenv); + } + if (!work_tree_env) { + retval = set_work_tree(gitdirenv); + /* config may override worktree */ + if (check_repository_format_gently(nongit_ok)) + return NULL; + return retval; + } + if (check_repository_format_gently(nongit_ok)) + return NULL; + retval = get_relative_cwd(buffer, sizeof(buffer) - 1, + get_git_work_tree()); + if (!retval || !*retval) + return NULL; + set_git_dir(make_absolute_path(gitdirenv)); + if (chdir(work_tree_env) < 0) + die_errno ("Could not chdir to '%s'", work_tree_env); + strcat(buffer, "/"); + return retval; +} + +static int cwd_contains_git_dir(const char **gitfile_dirp) +{ + const char *gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + *gitfile_dirp = gitfile_dir; + if (gitfile_dir) { + if (set_git_dir(gitfile_dir)) + die("Repository setup failed"); + return 1; + } + return is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT); +} + +static const char *setup_discovered_git_dir(const char *work_tree_env, + int offset, int len, char *cwd, int *nongit_ok) +{ + int root_len; + + inside_git_dir = 0; + if (!work_tree_env) + inside_work_tree = 1; + root_len = offset_1st_component(cwd); + git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len); + if (check_repository_format_gently(nongit_ok)) + return NULL; + if (offset == len) + return NULL; + + /* Make "offset" point to past the '/', and add a '/' at the end */ + offset++; + cwd[len++] = '/'; + cwd[len] = 0; + return cwd + offset; +} + +static const char *setup_bare_git_dir(const char *work_tree_env, + int offset, int len, char *cwd, int *nongit_ok) +{ + int root_len; + + inside_git_dir = 1; + if (!work_tree_env) + inside_work_tree = 0; + if (offset != len) { + if (chdir(cwd)) + die_errno("Cannot come back to cwd"); + root_len = offset_1st_component(cwd); + cwd[offset > root_len ? offset : root_len] = '\0'; + set_git_dir(cwd); + } else + set_git_dir("."); + check_repository_format_gently(nongit_ok); + return NULL; +} + +static const char *setup_nongit(const char *cwd, int *nongit_ok) +{ + if (!nongit_ok) + die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT); + if (chdir(cwd)) + die_errno("Cannot come back to cwd"); + *nongit_ok = 1; + return NULL; +} + +static dev_t get_device_or_die(const char *path, const char *prefix) +{ + struct stat buf; + if (stat(path, &buf)) + die_errno("failed to stat '%s%s%s'", + prefix ? prefix : "", + prefix ? "/" : "", path); + return buf.st_dev; +} + /* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. */ -const char *setup_git_directory_gently(int *nongit_ok) +static const char *setup_git_directory_gently_1(int *nongit_ok) { const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); static char cwd[PATH_MAX+1]; const char *gitdirenv; const char *gitfile_dir; - int len, offset, ceil_offset, root_len; + int len, offset, ceil_offset; dev_t current_device = 0; int one_filesystem = 1; - struct stat buf; /* * Let's assume that we are in a git repository. @@ -343,38 +451,8 @@ const char *setup_git_directory_gently(int *nongit_ok) * validation. */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); - if (gitdirenv) { - if (PATH_MAX - 40 < strlen(gitdirenv)) - die("'$%s' too big", GIT_DIR_ENVIRONMENT); - if (is_git_directory(gitdirenv)) { - static char buffer[1024 + 1]; - const char *retval; - - if (!work_tree_env) { - retval = set_work_tree(gitdirenv); - /* config may override worktree */ - if (check_repository_format_gently(nongit_ok)) - return NULL; - return retval; - } - if (check_repository_format_gently(nongit_ok)) - return NULL; - retval = get_relative_cwd(buffer, sizeof(buffer) - 1, - get_git_work_tree()); - if (!retval || !*retval) - return NULL; - set_git_dir(make_absolute_path(gitdirenv)); - if (chdir(work_tree_env) < 0) - die_errno ("Could not chdir to '%s'", work_tree_env); - strcat(buffer, "/"); - return retval; - } - if (nongit_ok) { - *nongit_ok = 1; - return NULL; - } - die("Not a git repository: '%s'", gitdirenv); - } + if (gitdirenv) + return setup_explicit_git_dir(gitdirenv, work_tree_env, nongit_ok); if (!getcwd(cwd, sizeof(cwd)-1)) die_errno("Unable to read current working directory"); @@ -396,49 +474,21 @@ const char *setup_git_directory_gently(int *nongit_ok) */ offset = len = strlen(cwd); one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); - if (one_filesystem) { - if (stat(".", &buf)) - die_errno("failed to stat '.'"); - current_device = buf.st_dev; - } + if (one_filesystem) + current_device = get_device_or_die(".", NULL); for (;;) { - gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); - if (gitfile_dir) { - if (set_git_dir(gitfile_dir)) - die("Repository setup failed"); - break; - } - if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) - break; - if (is_git_directory(".")) { - inside_git_dir = 1; - if (!work_tree_env) - inside_work_tree = 0; - if (offset != len) { - root_len = offset_1st_component(cwd); - cwd[offset > root_len ? offset : root_len] = '\0'; - set_git_dir(cwd); - } else - set_git_dir("."); - check_repository_format_gently(nongit_ok); - return NULL; - } + if (cwd_contains_git_dir(&gitfile_dir)) + return setup_discovered_git_dir(work_tree_env, offset, + len, cwd, nongit_ok); + if (is_git_directory(".")) + return setup_bare_git_dir(work_tree_env, offset, + len, cwd, nongit_ok); while (--offset > ceil_offset && cwd[offset] != '/'); - if (offset <= ceil_offset) { - if (nongit_ok) { - if (chdir(cwd)) - die_errno("Cannot come back to cwd"); - *nongit_ok = 1; - return NULL; - } - die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT); - } + if (offset <= ceil_offset) + return setup_nongit(cwd, nongit_ok); if (one_filesystem) { - if (stat("..", &buf)) { - cwd[offset] = '\0'; - die_errno("failed to stat '%s/..'", cwd); - } - if (buf.st_dev != current_device) { + dev_t parent_device = get_device_or_die("..", cwd); + if (parent_device != current_device) { if (nongit_ok) { if (chdir(cwd)) die_errno("Cannot come back to cwd"); @@ -455,22 +505,16 @@ const char *setup_git_directory_gently(int *nongit_ok) die_errno("Cannot change to '%s/..'", cwd); } } +} - inside_git_dir = 0; - if (!work_tree_env) - inside_work_tree = 1; - root_len = offset_1st_component(cwd); - git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len); - if (check_repository_format_gently(nongit_ok)) - return NULL; - if (offset == len) - return NULL; +const char *setup_git_directory_gently(int *nongit_ok) +{ + const char *prefix; - /* Make "offset" point to past the '/', and add a '/' at the end */ - offset++; - cwd[len++] = '/'; - cwd[len] = 0; - return cwd + offset; + prefix = setup_git_directory_gently_1(nongit_ok); + if (startup_info) + startup_info->have_repository = !nongit_ok || !*nongit_ok; + return prefix; } int git_config_perm(const char *var, const char *value) @@ -47,7 +47,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, { int i = 0, cur_depth = 0; struct commit_list *result = NULL; - struct object_array stack = {0, 0, NULL}; + struct object_array stack = OBJECT_ARRAY_INIT; struct commit *commit = NULL; while (commit || i < heads->nr || stack.nr) { diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 53bd7fcc4a..de38c7f7aa 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -48,11 +48,11 @@ test_expect_success 'attribute test' ' attr_check a/b/g a/b/g && attr_check b/g unspecified && attr_check a/b/h a/b/h && - attr_check a/b/d/g "a/b/d/*" - attr_check onoff unset - attr_check offon set - attr_check no unspecified - attr_check a/b/d/no "a/b/d/*" + attr_check a/b/d/g "a/b/d/*" && + attr_check onoff unset && + attr_check offon set && + attr_check no unspecified && + attr_check a/b/d/no "a/b/d/*" && attr_check a/b/d/yes unspecified ' diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh new file mode 100755 index 0000000000..d3225ada68 --- /dev/null +++ b/t/t0080-vcs-svn.sh @@ -0,0 +1,171 @@ +#!/bin/sh + +test_description='check infrastructure for svn importer' + +. ./test-lib.sh +uint32_max=4294967295 + +test_expect_success 'obj pool: store data' ' + cat <<-\EOF >expected && + 0 + 1 + EOF + + test-obj-pool <<-\EOF >actual && + alloc one 16 + set one 13 + test one 13 + reset one + EOF + test_cmp expected actual +' + +test_expect_success 'obj pool: NULL is offset ~0' ' + echo "$uint32_max" >expected && + echo null one | test-obj-pool >actual && + test_cmp expected actual +' + +test_expect_success 'obj pool: out-of-bounds access' ' + cat <<-EOF >expected && + 0 + 0 + $uint32_max + $uint32_max + 16 + 20 + $uint32_max + EOF + + test-obj-pool <<-\EOF >actual && + alloc one 16 + alloc two 16 + offset one 20 + offset two 20 + alloc one 5 + offset one 20 + free one 1 + offset one 20 + reset one + reset two + EOF + test_cmp expected actual +' + +test_expect_success 'obj pool: high-water mark' ' + cat <<-\EOF >expected && + 0 + 0 + 10 + 20 + 20 + 20 + EOF + + test-obj-pool <<-\EOF >actual && + alloc one 10 + committed one + alloc one 10 + commit one + committed one + alloc one 10 + free one 20 + committed one + reset one + EOF + test_cmp expected actual +' + +test_expect_success 'line buffer' ' + echo HELLO >expected1 && + printf "%s\n" "" HELLO >expected2 && + echo >expected3 && + printf "%s\n" "" Q | q_to_nul >expected4 && + printf "%s\n" foo "" >expected5 && + printf "%s\n" "" foo >expected6 && + + test-line-buffer <<-\EOF >actual1 && + 5 + HELLO + EOF + + test-line-buffer <<-\EOF >actual2 && + 0 + + 5 + HELLO + EOF + + q_to_nul <<-\EOF | + 1 + Q + EOF + test-line-buffer >actual3 && + + q_to_nul <<-\EOF | + 0 + + 1 + Q + EOF + test-line-buffer >actual4 && + + test-line-buffer <<-\EOF >actual5 && + 5 + foo + EOF + + test-line-buffer <<-\EOF >actual6 && + 0 + + 5 + foo + EOF + + test_cmp expected1 actual1 && + test_cmp expected2 actual2 && + test_cmp expected3 actual3 && + test_cmp expected4 actual4 && + test_cmp expected5 actual5 && + test_cmp expected6 actual6 +' + +test_expect_success 'string pool' ' + echo a does not equal b >expected.differ && + echo a equals a >expected.match && + echo equals equals equals >expected.matchmore && + + test-string-pool "a,--b" >actual.differ && + test-string-pool "a,a" >actual.match && + test-string-pool "equals-equals" >actual.matchmore && + test_must_fail test-string-pool a,a,a && + test_must_fail test-string-pool a && + + test_cmp expected.differ actual.differ && + test_cmp expected.match actual.match && + test_cmp expected.matchmore actual.matchmore +' + +test_expect_success 'treap sort' ' + cat <<-\EOF >unsorted && + 68 + 12 + 13 + 13 + 68 + 13 + 13 + 21 + 10 + 11 + 12 + 13 + 13 + EOF + sort unsorted >expected && + + test-treap <unsorted >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index bd8b60732b..2c8f01f668 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -3,183 +3,320 @@ test_description='test separate work tree' . ./test-lib.sh -test_rev_parse() { - name=$1 - shift - - test_expect_success "$name: is-bare-repository" \ - "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" - shift - [ $# -eq 0 ] && return - - test_expect_success "$name: is-inside-git-dir" \ - "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" - shift - [ $# -eq 0 ] && return - - test_expect_success "$name: is-inside-work-tree" \ - "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" - shift - [ $# -eq 0 ] && return - - test_expect_success "$name: prefix" \ - "test '$1' = \"\$(git rev-parse --show-prefix)\"" - shift - [ $# -eq 0 ] && return -} - -EMPTY_TREE=$(git write-tree) -mkdir -p work/sub/dir || exit 1 -mkdir -p work2 || exit 1 -mv .git repo.git || exit 1 - -say "core.worktree = relative path" -GIT_DIR=repo.git -GIT_CONFIG="$(pwd)"/$GIT_DIR/config -export GIT_DIR GIT_CONFIG -unset GIT_WORK_TREE -git config core.worktree ../work -test_rev_parse 'outside' false false false -cd work || exit 1 -GIT_DIR=../repo.git -GIT_CONFIG="$(pwd)"/$GIT_DIR/config -test_rev_parse 'inside' false false true '' -cd sub/dir || exit 1 -GIT_DIR=../../../repo.git -GIT_CONFIG="$(pwd)"/$GIT_DIR/config -test_rev_parse 'subdirectory' false false true sub/dir/ -cd ../../.. || exit 1 - -say "core.worktree = absolute path" -GIT_DIR=$(pwd)/repo.git -GIT_CONFIG=$GIT_DIR/config -git config core.worktree "$(pwd)/work" -test_rev_parse 'outside' false false false -cd work2 -test_rev_parse 'outside2' false false false -cd ../work || exit 1 -test_rev_parse 'inside' false false true '' -cd sub/dir || exit 1 -test_rev_parse 'subdirectory' false false true sub/dir/ -cd ../../.. || exit 1 - -say "GIT_WORK_TREE=relative path (override core.worktree)" -GIT_DIR=$(pwd)/repo.git -GIT_CONFIG=$GIT_DIR/config -git config core.worktree non-existent -GIT_WORK_TREE=work -export GIT_WORK_TREE -test_rev_parse 'outside' false false false -cd work2 -test_rev_parse 'outside' false false false -cd ../work || exit 1 -GIT_WORK_TREE=. -test_rev_parse 'inside' false false true '' -cd sub/dir || exit 1 -GIT_WORK_TREE=../.. -test_rev_parse 'subdirectory' false false true sub/dir/ -cd ../../.. || exit 1 - -mv work repo.git/work -mv work2 repo.git/work2 - -say "GIT_WORK_TREE=absolute path, work tree below git dir" -GIT_DIR=$(pwd)/repo.git -GIT_CONFIG=$GIT_DIR/config -GIT_WORK_TREE=$(pwd)/repo.git/work -test_rev_parse 'outside' false false false -cd repo.git || exit 1 -test_rev_parse 'in repo.git' false true false -cd objects || exit 1 -test_rev_parse 'in repo.git/objects' false true false -cd ../work2 || exit 1 -test_rev_parse 'in repo.git/work2' false true false -cd ../work || exit 1 -test_rev_parse 'in repo.git/work' false true true '' -cd sub/dir || exit 1 -test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/ -cd ../../../.. || exit 1 - -test_expect_success 'repo finds its work tree' ' - (cd repo.git && - : > work/sub/dir/untracked && - test sub/dir/untracked = "$(git ls-files --others)") -' - -test_expect_success 'repo finds its work tree from work tree, too' ' - (cd repo.git/work/sub/dir && - : > tracked && - git --git-dir=../../.. add tracked && - cd ../../.. && - test sub/dir/tracked = "$(git ls-files)") +test_expect_success 'setup' ' + EMPTY_TREE=$(git write-tree) && + EMPTY_BLOB=$(git hash-object -t blob --stdin </dev/null) && + CHANGED_BLOB=$(echo changed | git hash-object -t blob --stdin) && + ZEROES=0000000000000000000000000000000000000000 && + EMPTY_BLOB7=$(echo $EMPTY_BLOB | sed "s/\(.......\).*/\1/") && + CHANGED_BLOB7=$(echo $CHANGED_BLOB | sed "s/\(.......\).*/\1/") && + + mkdir -p work/sub/dir && + mkdir -p work2 && + mv .git repo.git +' + +test_expect_success 'setup: helper for testing rev-parse' ' + test_rev_parse() { + echo $1 >expected.bare && + echo $2 >expected.inside-git && + echo $3 >expected.inside-worktree && + if test $# -ge 4 + then + echo $4 >expected.prefix + fi && + + git rev-parse --is-bare-repository >actual.bare && + git rev-parse --is-inside-git-dir >actual.inside-git && + git rev-parse --is-inside-work-tree >actual.inside-worktree && + if test $# -ge 4 + then + git rev-parse --show-prefix >actual.prefix + fi && + + test_cmp expected.bare actual.bare && + test_cmp expected.inside-git actual.inside-git && + test_cmp expected.inside-worktree actual.inside-worktree && + if test $# -ge 4 + then + # rev-parse --show-prefix should output + # a single newline when at the top of the work tree, + # but we test for that separately. + test -z "$4" && ! test -s actual.prefix || + test_cmp expected.prefix actual.prefix + fi + } +' + +test_expect_success 'setup: core.worktree = relative path' ' + unset GIT_WORK_TREE; + GIT_DIR=repo.git && + GIT_CONFIG="$(pwd)"/$GIT_DIR/config && + export GIT_DIR GIT_CONFIG && + git config core.worktree ../work +' + +test_expect_success 'outside' ' + test_rev_parse false false false +' + +test_expect_success 'inside work tree' ' + ( + cd work && + GIT_DIR=../repo.git && + GIT_CONFIG="$(pwd)"/$GIT_DIR/config && + test_rev_parse false false true "" + ) +' + +test_expect_failure 'empty prefix is actually written out' ' + echo >expected && + ( + cd work && + GIT_DIR=../repo.git && + GIT_CONFIG="$(pwd)"/$GIT_DIR/config && + git rev-parse --show-prefix >../actual + ) && + test_cmp expected actual +' + +test_expect_success 'subdir of work tree' ' + ( + cd work/sub/dir && + GIT_DIR=../../../repo.git && + GIT_CONFIG="$(pwd)"/$GIT_DIR/config && + test_rev_parse false false true sub/dir/ + ) +' + +test_expect_success 'setup: core.worktree = absolute path' ' + unset GIT_WORK_TREE; + GIT_DIR=$(pwd)/repo.git && + GIT_CONFIG=$GIT_DIR/config && + export GIT_DIR GIT_CONFIG && + git config core.worktree "$(pwd)/work" +' + +test_expect_success 'outside' ' + test_rev_parse false false false && + ( + cd work2 && + test_rev_parse false false false + ) +' + +test_expect_success 'inside work tree' ' + ( + cd work && + test_rev_parse false false true "" + ) +' + +test_expect_success 'subdir of work tree' ' + ( + cd work/sub/dir && + test_rev_parse false false true sub/dir/ + ) +' + +test_expect_success 'setup: GIT_WORK_TREE=relative (override core.worktree)' ' + GIT_DIR=$(pwd)/repo.git && + GIT_CONFIG=$GIT_DIR/config && + git config core.worktree non-existent && + GIT_WORK_TREE=work && + export GIT_DIR GIT_CONFIG GIT_WORK_TREE +' + +test_expect_success 'outside' ' + test_rev_parse false false false && + ( + cd work2 && + test_rev_parse false false false + ) +' + +test_expect_success 'inside work tree' ' + ( + cd work && + GIT_WORK_TREE=. && + test_rev_parse false false true "" + ) +' + +test_expect_success 'subdir of work tree' ' + ( + cd work/sub/dir && + GIT_WORK_TREE=../.. && + test_rev_parse false false true sub/dir/ + ) +' + +test_expect_success 'setup: GIT_WORK_TREE=absolute, below git dir' ' + mv work repo.git/work && + mv work2 repo.git/work2 && + GIT_DIR=$(pwd)/repo.git && + GIT_CONFIG=$GIT_DIR/config && + GIT_WORK_TREE=$(pwd)/repo.git/work && + export GIT_DIR GIT_CONFIG GIT_WORK_TREE +' + +test_expect_success 'outside' ' + echo outside && + test_rev_parse false false false +' + +test_expect_success 'in repo.git' ' + ( + cd repo.git && + test_rev_parse false true false + ) && + ( + cd repo.git/objects && + test_rev_parse false true false + ) && + ( + cd repo.git/work2 && + test_rev_parse false true false + ) +' + +test_expect_success 'inside work tree' ' + ( + cd repo.git/work && + test_rev_parse false true true "" + ) +' + +test_expect_success 'subdir of work tree' ' + ( + cd repo.git/work/sub/dir && + test_rev_parse false true true sub/dir/ + ) +' + +test_expect_success 'find work tree from repo' ' + echo sub/dir/untracked >expected && + cat <<-\EOF >repo.git/work/.gitignore && + expected.* + actual.* + .gitignore + EOF + >repo.git/work/sub/dir/untracked && + ( + cd repo.git && + git ls-files --others --exclude-standard >../actual + ) && + test_cmp expected actual +' + +test_expect_success 'find work tree from work tree' ' + echo sub/dir/tracked >expected && + >repo.git/work/sub/dir/tracked && + ( + cd repo.git/work/sub/dir && + git --git-dir=../../.. add tracked + ) && + ( + cd repo.git && + git ls-files >../actual + ) && + test_cmp expected actual ' test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' - (cd repo.git/work/sub/dir && - GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ + ( + cd repo.git/work/sub/dir && + GIT_DIR=../../.. && + GIT_WORK_TREE=../.. && + GIT_PAGER= && + export GIT_DIR GIT_WORK_TREE GIT_PAGER && + git diff --exit-code tracked && - echo changed > tracked && - ! GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ - git diff --exit-code tracked) -' -cat > diff-index-cached.expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 A sub/dir/tracked -EOF -cat > diff-index.expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A sub/dir/tracked -EOF - - -test_expect_success 'git diff-index' ' - GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-index $EMPTY_TREE > result && - test_cmp diff-index.expected result && - GIT_DIR=repo.git git diff-index --cached $EMPTY_TREE > result && - test_cmp diff-index-cached.expected result -' -cat >diff-files.expected <<\EOF -:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M sub/dir/tracked -EOF - -test_expect_success 'git diff-files' ' - GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-files > result && - test_cmp diff-files.expected result -' - -cat >diff-TREE.expected <<\EOF -diff --git a/sub/dir/tracked b/sub/dir/tracked -new file mode 100644 -index 0000000..5ea2ed4 ---- /dev/null -+++ b/sub/dir/tracked -@@ -0,0 +1 @@ -+changed -EOF -cat >diff-TREE-cached.expected <<\EOF -diff --git a/sub/dir/tracked b/sub/dir/tracked -new file mode 100644 -index 0000000..e69de29 -EOF -cat >diff-FILES.expected <<\EOF -diff --git a/sub/dir/tracked b/sub/dir/tracked -index e69de29..5ea2ed4 100644 ---- a/sub/dir/tracked -+++ b/sub/dir/tracked -@@ -0,0 +1 @@ -+changed -EOF - -test_expect_success 'git diff' ' - GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff $EMPTY_TREE > result && - test_cmp diff-TREE.expected result && - GIT_DIR=repo.git git diff --cached $EMPTY_TREE > result && - test_cmp diff-TREE-cached.expected result && - GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff > result && - test_cmp diff-FILES.expected result + echo changed >tracked && + test_must_fail git diff --exit-code tracked + ) +' + +test_expect_success 'diff-index respects work tree under .git dir' ' + cat >diff-index-cached.expected <<-EOF && + :000000 100644 $ZEROES $EMPTY_BLOB A sub/dir/tracked + EOF + cat >diff-index.expected <<-EOF && + :000000 100644 $ZEROES $ZEROES A sub/dir/tracked + EOF + + ( + GIT_DIR=repo.git && + GIT_WORK_TREE=repo.git/work && + export GIT_DIR GIT_WORK_TREE && + git diff-index $EMPTY_TREE >diff-index.actual && + git diff-index --cached $EMPTY_TREE >diff-index-cached.actual + ) && + test_cmp diff-index.expected diff-index.actual && + test_cmp diff-index-cached.expected diff-index-cached.actual +' + +test_expect_success 'diff-files respects work tree under .git dir' ' + cat >diff-files.expected <<-EOF && + :100644 100644 $EMPTY_BLOB $ZEROES M sub/dir/tracked + EOF + + ( + GIT_DIR=repo.git && + GIT_WORK_TREE=repo.git/work && + export GIT_DIR GIT_WORK_TREE && + git diff-files >diff-files.actual + ) && + test_cmp diff-files.expected diff-files.actual +' + +test_expect_success 'git diff respects work tree under .git dir' ' + cat >diff-TREE.expected <<-EOF && + diff --git a/sub/dir/tracked b/sub/dir/tracked + new file mode 100644 + index 0000000..$CHANGED_BLOB7 + --- /dev/null + +++ b/sub/dir/tracked + @@ -0,0 +1 @@ + +changed + EOF + cat >diff-TREE-cached.expected <<-EOF && + diff --git a/sub/dir/tracked b/sub/dir/tracked + new file mode 100644 + index 0000000..$EMPTY_BLOB7 + EOF + cat >diff-FILES.expected <<-EOF && + diff --git a/sub/dir/tracked b/sub/dir/tracked + index $EMPTY_BLOB7..$CHANGED_BLOB7 100644 + --- a/sub/dir/tracked + +++ b/sub/dir/tracked + @@ -0,0 +1 @@ + +changed + EOF + + ( + GIT_DIR=repo.git && + GIT_WORK_TREE=repo.git/work && + export GIT_DIR GIT_WORK_TREE && + git diff $EMPTY_TREE >diff-TREE.actual && + git diff --cached $EMPTY_TREE >diff-TREE-cached.actual && + git diff >diff-FILES.actual + ) && + test_cmp diff-TREE.expected diff-TREE.actual && + test_cmp diff-TREE-cached.expected diff-TREE-cached.actual && + test_cmp diff-FILES.expected diff-FILES.actual ' test_expect_success 'git grep' ' - (cd repo.git/work/sub && - GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked) + echo dir/tracked >expected.grep && + ( + cd repo.git/work/sub && + GIT_DIR=../.. && + GIT_WORK_TREE=.. && + export GIT_DIR GIT_WORK_TREE && + git grep -l changed >../../../actual.grep + ) && + test_cmp expected.grep actual.grep ' test_expect_success 'git commit' ' @@ -191,14 +328,14 @@ test_expect_success 'git commit' ' test_expect_success 'absolute pathspec should fail gracefully' ' ( - cd repo.git || exit 1 - git config --unset core.worktree + cd repo.git && + test_might_fail git config --unset core.worktree && test_must_fail git log HEAD -- /home ) ' test_expect_success 'make_relative_path handles double slashes in GIT_DIR' ' - : > dummy_file + >dummy_file echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file && git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file ' diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index 361a10aeb1..8ab333dbd9 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -98,7 +98,7 @@ time_notes () { for mode in no-notes notes do echo $mode - /usr/bin/time sh ../time_notes $mode $1 + /usr/bin/time "$SHELL_PATH" ../time_notes $mode $1 done } diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index b63f4e2d67..37cb89ab53 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -21,38 +21,62 @@ test_expect_success setup ' git tag base ' -test_expect_success 'auto fixup' ' +test_auto_fixup() { git reset --hard base && echo 1 >file1 && git add -u && test_tick && git commit -m "fixup! first" - git tag final-fixup && + git tag $1 && test_tick && - git rebase --autosquash -i HEAD^^^ && + git rebase $2 -i HEAD^^^ && git log --oneline >actual && test 3 = $(wc -l <actual) && - git diff --exit-code final-fixup && + git diff --exit-code $1 && test 1 = "$(git cat-file blob HEAD^:file1)" && test 1 = $(git cat-file commit HEAD^ | grep first | wc -l) +} + +test_expect_success 'auto fixup (option)' ' + test_auto_fixup final-fixup-option --autosquash +' + +test_expect_success 'auto fixup (config)' ' + git config rebase.autosquash true && + test_auto_fixup final-fixup-config-true && + test_must_fail test_auto_fixup fixup-config-true-no --no-autosquash && + git config rebase.autosquash false && + test_must_fail test_auto_fixup final-fixup-config-false ' -test_expect_success 'auto squash' ' +test_auto_squash() { git reset --hard base && echo 1 >file1 && git add -u && test_tick && git commit -m "squash! first" - git tag final-squash && + git tag $1 && test_tick && - git rebase --autosquash -i HEAD^^^ && + git rebase $2 -i HEAD^^^ && git log --oneline >actual && test 3 = $(wc -l <actual) && - git diff --exit-code final-squash && + git diff --exit-code $1 && test 1 = "$(git cat-file blob HEAD^:file1)" && test 2 = $(git cat-file commit HEAD^ | grep first | wc -l) +} + +test_expect_success 'auto squash (option)' ' + test_auto_squash final-squash --autosquash +' + +test_expect_success 'auto squash (config)' ' + git config rebase.autosquash true && + test_auto_squash final-squash-config-true && + test_must_fail test_auto_squash squash-config-true-no --no-autosquash && + git config rebase.autosquash false && + test_must_fail test_auto_squash final-squash-config-false ' test_expect_success 'misspelled auto squash' ' diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index e25cf8039a..607bf25d8f 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -38,6 +38,26 @@ test_expect_success 'failed cherry-pick does not advance HEAD' ' test "$head" = "$newhead" ' +test_expect_success 'advice from failed cherry-pick' " + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + picked=\$(git rev-parse --short picked) && + cat <<-EOF >expected && + error: could not apply \$picked... picked + hint: after resolving the conflicts, mark the corrected paths + hint: with 'git add <paths>' or 'git rm <paths>' + hint: and commit the result with 'git commit -c \$picked' + EOF + test_must_fail git cherry-pick picked 2>actual && + + test_cmp expected actual +" + test_expect_success 'failed cherry-pick produces dirty index' ' git checkout -f initial^0 && diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh index f90ed3da3e..8e09fd0319 100755 --- a/t/t3508-cherry-pick-many-commits.sh +++ b/t/t3508-cherry-pick-many-commits.sh @@ -4,6 +4,18 @@ test_description='test cherry-picking many commits' . ./test-lib.sh +check_head_differs_from() { + head=$(git rev-parse --verify HEAD) && + arg=$(git rev-parse --verify "$1") && + test "$head" != "$arg" +} + +check_head_equals() { + head=$(git rev-parse --verify HEAD) && + arg=$(git rev-parse --verify "$1") && + test "$head" = "$arg" +} + test_expect_success setup ' echo first > file1 && git add file1 && @@ -23,13 +35,55 @@ test_expect_success setup ' ' test_expect_success 'cherry-pick first..fourth works' ' + cat <<-\EOF >expected && + [master OBJID] second + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + [master OBJID] third + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + [master OBJID] fourth + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + EOF + + git checkout -f master && + git reset --hard first && + test_tick && + git cherry-pick first..fourth >actual && + git diff --quiet other && + git diff --quiet HEAD other && + + sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy && + test_cmp expected actual.fuzzy && + check_head_differs_from fourth +' + +test_expect_success 'cherry-pick --strategy resolve first..fourth works' ' + cat <<-\EOF >expected && + Trying simple merge. + [master OBJID] second + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + Trying simple merge. + [master OBJID] third + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + Trying simple merge. + [master OBJID] fourth + Author: A U Thor <author@example.com> + 1 files changed, 1 insertions(+), 0 deletions(-) + EOF + git checkout -f master && git reset --hard first && test_tick && - git cherry-pick first..fourth && + git cherry-pick --strategy resolve first..fourth >actual && git diff --quiet other && git diff --quiet HEAD other && - test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" + sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy && + test_cmp expected actual.fuzzy && + check_head_differs_from fourth ' test_expect_success 'cherry-pick --ff first..fourth works' ' @@ -39,7 +93,7 @@ test_expect_success 'cherry-pick --ff first..fourth works' ' git cherry-pick --ff first..fourth && git diff --quiet other && git diff --quiet HEAD other && - test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify fourth)" + check_head_equals fourth ' test_expect_success 'cherry-pick -n first..fourth works' ' @@ -89,7 +143,7 @@ test_expect_success 'cherry-pick -3 fourth works' ' git cherry-pick -3 fourth && git diff --quiet other && git diff --quiet HEAD other && - test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" + check_head_differs_from fourth ' test_expect_success 'cherry-pick --stdin works' ' @@ -99,7 +153,7 @@ test_expect_success 'cherry-pick --stdin works' ' git rev-list --reverse first..fourth | git cherry-pick --stdin && git diff --quiet other && git diff --quiet HEAD other && - test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" + check_head_differs_from fourth ' test_done diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh new file mode 100755 index 0000000000..a5ccdbf8fc --- /dev/null +++ b/t/t3509-cherry-pick-merge-df.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='Test cherry-pick with directory/file conflicts' +. ./test-lib.sh + +test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' ' + mkdir a && + >a/f && + git add a && + git commit -m a && + + mkdir b && + ln -s ../a b/a && + git add b && + git commit -m b && + + git checkout -b branch && + rm b/a && + mv a b/a && + ln -s b/a a && + git add . && + git commit -m swap && + + >f1 && + git add f1 && + git commit -m f1 +' + +test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' ' + git reset --hard && + git checkout master^0 && + git cherry-pick branch +' + +test_done diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh new file mode 100755 index 0000000000..a52d94ae21 --- /dev/null +++ b/t/t4111-apply-subdir.sh @@ -0,0 +1,142 @@ +#!/bin/sh + +test_description='patching from inconvenient places' + +. ./test-lib.sh + +test_expect_success 'setup' ' + cat >patch <<-\EOF && + diff file.orig file + --- a/file.orig + +++ b/file + @@ -1 +1,2 @@ + 1 + +2 + EOF + patch="$(pwd)/patch" && + + echo 1 >preimage && + printf "%s\n" 1 2 >postimage && + echo 3 >other && + + test_tick && + git commit --allow-empty -m basis +' + +test_expect_success 'setup: subdir' ' + reset_subdir() { + git reset && + mkdir -p sub/dir/b && + mkdir -p objects && + cp "$1" file && + cp "$1" objects/file && + cp "$1" sub/dir/file && + cp "$1" sub/dir/b/file && + git add file sub/dir/file sub/dir/b/file objects/file && + cp "$2" file && + cp "$2" sub/dir/file && + cp "$2" sub/dir/b/file && + cp "$2" objects/file && + test_might_fail git update-index --refresh -q + } +' + +test_expect_success 'apply from subdir of toplevel' ' + cp postimage expected && + reset_subdir other preimage && + ( + cd sub/dir && + git apply "$patch" + ) && + test_cmp expected sub/dir/file +' + +test_expect_success 'apply --cached from subdir of toplevel' ' + cp postimage expected && + cp other expected.working && + reset_subdir preimage other && + ( + cd sub/dir && + git apply --cached "$patch" + ) && + git show :sub/dir/file >actual && + test_cmp expected actual && + test_cmp expected.working sub/dir/file +' + +test_expect_success 'apply --index from subdir of toplevel' ' + cp postimage expected && + reset_subdir preimage other && + ( + cd sub/dir && + test_must_fail git apply --index "$patch" + ) && + reset_subdir other preimage && + ( + cd sub/dir && + test_must_fail git apply --index "$patch" + ) && + reset_subdir preimage preimage && + ( + cd sub/dir && + git apply --index "$patch" + ) && + git show :sub/dir/file >actual && + test_cmp expected actual && + test_cmp expected sub/dir/file +' + +test_expect_success 'apply from .git dir' ' + cp postimage expected && + cp preimage .git/file && + cp preimage .git/objects/file + ( + cd .git && + git apply "$patch" + ) && + test_cmp expected .git/file +' + +test_expect_success 'apply from subdir of .git dir' ' + cp postimage expected && + cp preimage .git/file && + cp preimage .git/objects/file + ( + cd .git/objects && + git apply "$patch" + ) && + test_cmp expected .git/objects/file +' + +test_expect_success 'apply --cached from .git dir' ' + cp postimage expected && + cp other expected.working && + cp other .git/file && + reset_subdir preimage other && + ( + cd .git && + git apply --cached "$patch" + ) && + git show :file >actual && + test_cmp expected actual && + test_cmp expected.working file && + test_cmp expected.working .git/file +' + +test_expect_success 'apply --cached from subdir of .git dir' ' + cp postimage expected && + cp preimage expected.subdir && + cp other .git/file && + cp other .git/objects/file && + reset_subdir preimage other && + ( + cd .git/objects && + git apply --cached "$patch" + ) && + git show :file >actual && + git show :objects/file >actual.subdir && + test_cmp expected actual && + test_cmp expected.subdir actual.subdir +' + +test_done diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 70856d07ed..36255d608a 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -4,237 +4,391 @@ # test_description='git rerere + +! [fifth] version1 + ! [first] first + ! [fourth] version1 + ! [master] initial + ! [second] prefer first over second + ! [third] version2 +------ + + [third] version2 ++ [fifth] version1 + + [fourth] version1 ++ + + [third^] third + - [second] prefer first over second + + + [first] first + + [second^] second +++++++ [master] initial ' . ./test-lib.sh -test_expect_success 'setup' " - cat > a1 <<- EOF && +test_expect_success 'setup' ' + cat >a1 <<-\EOF && Some title ========== - Whether 'tis nobler in the mind to suffer + Whether '\''tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? To die: to sleep; No more; and by a sleep to say we end The heart-ache and the thousand natural shocks - That flesh is heir to, 'tis a consummation - Devoutly to be wish'd. + That flesh is heir to, '\''tis a consummation + Devoutly to be wish'\''d. EOF git add a1 && + test_tick && git commit -q -a -m initial && - git checkout -b first && - cat >> a1 <<- EOF && + cat >>a1 <<-\EOF && Some title ========== To die, to sleep; - To sleep: perchance to dream: ay, there's the rub; + To sleep: perchance to dream: ay, there'\''s the rub; For in that sleep of death what dreams may come When we have shuffled off this mortal coil, - Must give us pause: there's the respect + Must give us pause: there'\''s the respect That makes calamity of so long life; EOF + + git checkout -b first && + test_tick && git commit -q -a -m first && git checkout -b second master && git show first:a1 | - sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 && - echo '* END *' >>a1 && + sed -e "s/To die, t/To die! T/" -e "s/Some title/Some Title/" >a1 && + echo "* END *" >>a1 && + test_tick && git commit -q -a -m second -" +' test_expect_success 'nothing recorded without rerere' ' - (rm -rf .git/rr-cache; git config rerere.enabled false) && + rm -rf .git/rr-cache && + git config rerere.enabled false && test_must_fail git merge first && ! test -d .git/rr-cache ' -# activate rerere, old style -test_expect_success 'conflicting merge' ' +test_expect_success 'activate rerere, old style (conflicting merge)' ' git reset --hard && mkdir .git/rr-cache && - git config --unset rerere.enabled && - test_must_fail git merge first -' + test_might_fail git config --unset rerere.enabled && + test_must_fail git merge first && -sha1=$(perl -pe 's/ .*//' .git/MERGE_RR) -rr=.git/rr-cache/$sha1 -test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage" + sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + rr=.git/rr-cache/$sha1 && + grep "^=======\$" $rr/preimage && + ! test -f $rr/postimage && + ! test -f $rr/thisimage +' test_expect_success 'rerere.enabled works, too' ' rm -rf .git/rr-cache && git config rerere.enabled true && git reset --hard && test_must_fail git merge first && + + sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + rr=.git/rr-cache/$sha1 && grep ^=======$ $rr/preimage ' -test_expect_success 'no postimage or thisimage yet' \ - "test ! -f $rr/postimage -a ! -f $rr/thisimage" +test_expect_success 'set up rr-cache' ' + rm -rf .git/rr-cache && + git config rerere.enabled true && + git reset --hard && + test_must_fail git merge first && + sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + rr=.git/rr-cache/$sha1 +' -test_expect_success 'preimage has right number of lines' ' +test_expect_success 'rr-cache looks sane' ' + # no postimage or thisimage yet + ! test -f $rr/postimage && + ! test -f $rr/thisimage && + # preimage has right number of lines cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) && + echo $cnt && test $cnt = 13 - ' -git show first:a1 > a1 - -cat > expect << EOF ---- a/a1 -+++ b/a1 -@@ -1,4 +1,4 @@ --Some Title -+Some title - ========== - Whether 'tis nobler in the mind to suffer - The slings and arrows of outrageous fortune, -@@ -8,21 +8,11 @@ - The heart-ache and the thousand natural shocks - That flesh is heir to, 'tis a consummation - Devoutly to be wish'd. --<<<<<<< --Some Title --========== --To die! To sleep; --======= - Some title - ========== - To die, to sleep; -->>>>>>> - To sleep: perchance to dream: ay, there's the rub; - For in that sleep of death what dreams may come - When we have shuffled off this mortal coil, - Must give us pause: there's the respect - That makes calamity of so long life; --<<<<<<< --======= --* END * -->>>>>>> -EOF -git rerere diff > out - -test_expect_success 'rerere diff' 'test_cmp expect out' - -cat > expect << EOF -a1 -EOF - -git rerere status > out - -test_expect_success 'rerere status' 'test_cmp expect out' - -test_expect_success 'commit succeeds' \ - "git commit -q -a -m 'prefer first over second'" - -test_expect_success 'recorded postimage' "test -f $rr/postimage" - -test_expect_success 'another conflicting merge' ' - git checkout -b third master && - git show second^:a1 | sed "s/To die: t/To die! T/" > a1 && - git commit -q -a -m third && - test_must_fail git pull . first +test_expect_success 'rerere diff' ' + git show first:a1 >a1 && + cat >expect <<-\EOF && + --- a/a1 + +++ b/a1 + @@ -1,4 +1,4 @@ + -Some Title + +Some title + ========== + Whether '\''tis nobler in the mind to suffer + The slings and arrows of outrageous fortune, + @@ -8,21 +8,11 @@ + The heart-ache and the thousand natural shocks + That flesh is heir to, '\''tis a consummation + Devoutly to be wish'\''d. + -<<<<<<< + -Some Title + -========== + -To die! To sleep; + -======= + Some title + ========== + To die, to sleep; + ->>>>>>> + To sleep: perchance to dream: ay, there'\''s the rub; + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, + Must give us pause: there'\''s the respect + That makes calamity of so long life; + -<<<<<<< + -======= + -* END * + ->>>>>>> + EOF + git rerere diff >out && + test_cmp expect out ' -git show first:a1 | sed 's/To die: t/To die! T/' > expect -test_expect_success 'rerere kicked in' "! grep ^=======$ a1" +test_expect_success 'rerere status' ' + echo a1 >expect && + git rerere status >out && + test_cmp expect out +' -test_expect_success 'rerere prefers first change' 'test_cmp a1 expect' +test_expect_success 'first postimage wins' ' + git show first:a1 | sed "s/To die: t/To die! T/" >expect && -rm $rr/postimage -echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR + git commit -q -a -m "prefer first over second" && + test -f $rr/postimage && -test_expect_success 'rerere clear' 'git rerere clear' + oldmtimepost=$(test-chmtime -v -60 $rr/postimage | cut -f 1) && -test_expect_success 'clear removed the directory' "test ! -d $rr" + git checkout -b third master && + git show second^:a1 | sed "s/To die: t/To die! T/" >a1 && + git commit -q -a -m third && -mkdir $rr -echo Hello > $rr/preimage -echo World > $rr/postimage + test_must_fail git pull . first && + # rerere kicked in + ! grep "^=======\$" a1 && + test_cmp expect a1 +' -sha2=4000000000000000000000000000000000000000 -rr2=.git/rr-cache/$sha2 -mkdir $rr2 -echo Hello > $rr2/preimage +test_expect_success 'rerere updates postimage timestamp' ' + newmtimepost=$(test-chmtime -v +0 $rr/postimage | cut -f 1) && + test $oldmtimepost -lt $newmtimepost +' -almost_15_days_ago=$((60-15*86400)) -just_over_15_days_ago=$((-1-15*86400)) -almost_60_days_ago=$((60-60*86400)) -just_over_60_days_ago=$((-1-60*86400)) +test_expect_success 'rerere clear' ' + rm $rr/postimage && + echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR && + git rerere clear && + ! test -d $rr +' -test-chmtime =$almost_60_days_ago $rr/preimage -test-chmtime =$almost_15_days_ago $rr2/preimage +test_expect_success 'set up for garbage collection tests' ' + mkdir -p $rr && + echo Hello >$rr/preimage && + echo World >$rr/postimage && -test_expect_success 'garbage collection (part1)' 'git rerere gc' + sha2=4000000000000000000000000000000000000000 && + rr2=.git/rr-cache/$sha2 && + mkdir $rr2 && + echo Hello >$rr2/preimage && -test_expect_success 'young records still live' \ - "test -f $rr/preimage && test -f $rr2/preimage" + almost_15_days_ago=$((60-15*86400)) && + just_over_15_days_ago=$((-1-15*86400)) && + almost_60_days_ago=$((60-60*86400)) && + just_over_60_days_ago=$((-1-60*86400)) && -test-chmtime =$just_over_60_days_ago $rr/preimage -test-chmtime =$just_over_15_days_ago $rr2/preimage + test-chmtime =$just_over_60_days_ago $rr/preimage && + test-chmtime =$almost_60_days_ago $rr/postimage && + test-chmtime =$almost_15_days_ago $rr2/preimage +' -test_expect_success 'garbage collection (part2)' 'git rerere gc' +test_expect_success 'gc preserves young or recently used records' ' + git rerere gc && + test -f $rr/preimage && + test -f $rr2/preimage +' -test_expect_success 'old records rest in peace' \ - "test ! -f $rr/preimage && test ! -f $rr2/preimage" +test_expect_success 'old records rest in peace' ' + test-chmtime =$just_over_60_days_ago $rr/postimage && + test-chmtime =$just_over_15_days_ago $rr2/preimage && + git rerere gc && + ! test -f $rr/preimage && + ! test -f $rr2/preimage +' -test_expect_success 'file2 added differently in two branches' ' +test_expect_success 'setup: file2 added differently in two branches' ' git reset --hard && + git checkout -b fourth && - echo Hallo > file2 && + echo Hallo >file2 && git add file2 && + test_tick && git commit -m version1 && + git checkout third && - echo Bello > file2 && + echo Bello >file2 && git add file2 && + test_tick && git commit -m version2 && + test_must_fail git merge fourth && - echo Cello > file2 && + echo Cello >file2 && git add file2 && git commit -m resolution ' test_expect_success 'resolution was recorded properly' ' + echo Cello >expected && + git reset --hard HEAD~2 && git checkout -b fifth && - echo Hallo > file3 && + + echo Hallo >file3 && git add file3 && + test_tick && git commit -m version1 && + git checkout third && - echo Bello > file3 && + echo Bello >file3 && git add file3 && + test_tick && git commit -m version2 && git tag version2 && + test_must_fail git merge fifth && - test Cello = "$(cat file3)" && - test 0 != $(git ls-files -u | wc -l) + test_cmp expected file3 && + test_must_fail git update-index --refresh ' test_expect_success 'rerere.autoupdate' ' - git config rerere.autoupdate true + git config rerere.autoupdate true && git reset --hard && git checkout version2 && test_must_fail git merge fifth && - test 0 = $(git ls-files -u | wc -l) + git update-index --refresh ' test_expect_success 'merge --rerere-autoupdate' ' - git config --unset rerere.autoupdate + test_might_fail git config --unset rerere.autoupdate && git reset --hard && git checkout version2 && test_must_fail git merge --rerere-autoupdate fifth && - test 0 = $(git ls-files -u | wc -l) + git update-index --refresh ' test_expect_success 'merge --no-rerere-autoupdate' ' - git config rerere.autoupdate true + headblob=$(git rev-parse version2:file3) && + mergeblob=$(git rev-parse fifth:file3) && + cat >expected <<-EOF && + 100644 $headblob 2 file3 + 100644 $mergeblob 3 file3 + EOF + + git config rerere.autoupdate true && git reset --hard && git checkout version2 && test_must_fail git merge --no-rerere-autoupdate fifth && - test 2 = $(git ls-files -u | wc -l) + git ls-files -u >actual && + test_cmp expected actual +' + +test_expect_success 'set up an unresolved merge' ' + headblob=$(git rev-parse version2:file3) && + mergeblob=$(git rev-parse fifth:file3) && + cat >expected.unresolved <<-EOF && + 100644 $headblob 2 file3 + 100644 $mergeblob 3 file3 + EOF + + test_might_fail git config --unset rerere.autoupdate && + git reset --hard && + git checkout version2 && + fifth=$(git rev-parse fifth) && + echo "$fifth branch 'fifth' of ." | + git fmt-merge-msg >msg && + ancestor=$(git merge-base version2 fifth) && + test_must_fail git merge-recursive "$ancestor" -- HEAD fifth && + + git ls-files --stage >failedmerge && + cp file3 file3.conflict && + + git ls-files -u >actual && + test_cmp expected.unresolved actual +' + +test_expect_success 'explicit rerere' ' + test_might_fail git config --unset rerere.autoupdate && + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + test_must_fail git update-index --refresh -q && + + git rerere && + git ls-files -u >actual && + test_cmp expected.unresolved actual +' + +test_expect_success 'explicit rerere with autoupdate' ' + git config rerere.autoupdate true && + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + test_must_fail git update-index --refresh -q && + + git rerere && + git update-index --refresh +' + +test_expect_success 'explicit rerere --rerere-autoupdate overrides' ' + git config rerere.autoupdate false && + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + git rerere && + git ls-files -u >actual1 && + + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + git rerere --rerere-autoupdate && + git update-index --refresh && + + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + git rerere --rerere-autoupdate --no-rerere-autoupdate && + git ls-files -u >actual2 && + + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + git rerere --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate && + git update-index --refresh && + + test_cmp expected.unresolved actual1 && + test_cmp expected.unresolved actual2 +' + +test_expect_success 'rerere --no-no-rerere-autoupdate' ' + git rm -fr --cached . && + git update-index --index-info <failedmerge && + cp file3.conflict file3 && + test_must_fail git rerere --no-no-rerere-autoupdate 2>err && + grep [Uu]sage err && + test_must_fail git update-index --refresh +' + +test_expect_success 'rerere -h' ' + test_must_fail git rerere -h >help && + grep [Uu]sage help ' test_done diff --git a/t/t5525-fetch-tagopt.sh b/t/t5525-fetch-tagopt.sh new file mode 100755 index 0000000000..4fbf7a120f --- /dev/null +++ b/t/t5525-fetch-tagopt.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description='tagopt variable affects "git fetch" and is overridden by commandline.' + +. ./test-lib.sh + +setup_clone () { + git clone --mirror . $1 && + git remote add remote_$1 $1 && + (cd $1 && + git tag tag_$1) +} + +test_expect_success setup ' + test_commit test && + setup_clone one && + git config remote.remote_one.tagopt --no-tags && + setup_clone two && + git config remote.remote_two.tagopt --tags + ' + +test_expect_success "fetch with tagopt=--no-tags does not get tag" ' + git fetch remote_one && + test_must_fail git show-ref tag_one + ' + +test_expect_success "fetch --tags with tagopt=--no-tags gets tag" ' + git fetch --tags remote_one && + git show-ref tag_one + ' + +test_expect_success "fetch --no-tags with tagopt=--tags does not get tag" ' + git fetch --no-tags remote_two && + test_must_fail git show-ref tag_two + ' + +test_expect_success "fetch with tagopt=--tags gets tag" ' + git fetch remote_two && + git show-ref tag_two + ' +test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 8abb71afcd..4431dfd02b 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -178,8 +178,14 @@ test_expect_success 'clone respects global branch.autosetuprebase' ' test_expect_success 'respect url-encoding of file://' ' git init x+y && - test_must_fail git clone "file://$PWD/x+y" xy-url && - git clone "file://$PWD/x%2By" xy-url + git clone "file://$PWD/x+y" xy-url-1 && + git clone "file://$PWD/x%2By" xy-url-2 +' + +test_expect_success 'do not query-string-decode + in URLs' ' + rm -rf x+y && + git init "x y" && + test_must_fail git clone "file://$PWD/x+y" xy-no-plus ' test_expect_success 'do not respect url-encoding of non-url path' ' diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 0144d9e858..62197a3d35 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -3,175 +3,231 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Merge base computation. +test_description='Merge base and parent list computation. ' . ./test-lib.sh -T=$(git write-tree) - -M=1130000000 -Z=+0000 - -GIT_COMMITTER_EMAIL=git@comm.iter.xz -GIT_COMMITTER_NAME='C O Mmiter' -GIT_AUTHOR_NAME='A U Thor' -GIT_AUTHOR_EMAIL=git@au.thor.xz -export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL - -doit() { - OFFSET=$1; shift - NAME=$1; shift - PARENTS= - for P - do - PARENTS="${PARENTS}-p $P " - done - GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" - GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE - commit=$(echo $NAME | git commit-tree $T $PARENTS) - echo $commit >.git/refs/tags/$NAME - echo $commit -} - -# E---D---C---B---A -# \'-_ \ \ -# \ `---------G \ -# \ \ -# F----------------H - -# Setup... -E=$(doit 5 E) -D=$(doit 4 D $E) -F=$(doit 6 F $E) -C=$(doit 3 C $D) -B=$(doit 2 B $C) -A=$(doit 1 A $B) -G=$(doit 7 G $B $E) -H=$(doit 8 H $A $F) - -test_expect_success 'compute merge-base (single)' \ - 'MB=$(git merge-base G H) && - expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"' - -test_expect_success 'compute merge-base (all)' \ - 'MB=$(git merge-base --all G H) && - expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"' - -test_expect_success 'compute merge-base with show-branch' \ - 'MB=$(git show-branch --merge-base G H) && - expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"' - -# Setup for second test to demonstrate that relying on timestamps in a -# distributed SCM to provide a _consistent_ partial ordering of commits -# leads to insanity. -# -# Relative -# Structure timestamps -# -# PL PR +4 +4 -# / \/ \ / \/ \ -# L2 C2 R2 +3 -1 +3 -# | | | | | | -# L1 C1 R1 +2 -2 +2 -# | | | | | | -# L0 C0 R0 +1 -3 +1 -# \ | / \ | / -# S 0 -# -# The left and right chains of commits can be of any length and complexity as -# long as all of the timestamps are greater than that of S. +test_expect_success 'setup' ' + T=$(git write-tree) && -S=$(doit 0 S) + M=1130000000 && + Z=+0000 && -C0=$(doit -3 C0 $S) -C1=$(doit -2 C1 $C0) -C2=$(doit -1 C2 $C1) + GIT_COMMITTER_EMAIL=git@comm.iter.xz && + GIT_COMMITTER_NAME="C O Mmiter" && + GIT_AUTHOR_NAME="A U Thor" && + GIT_AUTHOR_EMAIL=git@au.thor.xz && + export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && -L0=$(doit 1 L0 $S) -L1=$(doit 2 L1 $L0) -L2=$(doit 3 L2 $L1) + doit() { + OFFSET=$1 && + NAME=$2 && + shift 2 && -R0=$(doit 1 R0 $S) -R1=$(doit 2 R1 $R0) -R2=$(doit 3 R2 $R1) + PARENTS= && + for P + do + PARENTS="${PARENTS}-p $P " + done && -PL=$(doit 4 PL $L2 $C2) -PR=$(doit 4 PR $C2 $R2) + GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" && + GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE && + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE && -test_expect_success 'compute merge-base (single)' \ - 'MB=$(git merge-base PL PR) && - expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"' + commit=$(echo $NAME | git commit-tree $T $PARENTS) && -test_expect_success 'compute merge-base (all)' \ - 'MB=$(git merge-base --all PL PR) && - expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"' + echo $commit >.git/refs/tags/$NAME && + echo $commit + } +' -# Another set to demonstrate base between one commit and a merge -# in the documentation. -# -# * C (MMC) * B (MMB) * A (MMA) -# * o * o * o -# * o * o * o -# * o * o * o -# * o | _______/ -# | |/ -# | * 1 (MM1) -# | _______/ -# |/ -# * root (MMR) +test_expect_success 'set up G and H' ' + # E---D---C---B---A + # \"-_ \ \ + # \ `---------G \ + # \ \ + # F----------------H + E=$(doit 5 E) && + D=$(doit 4 D $E) && + F=$(doit 6 F $E) && + C=$(doit 3 C $D) && + B=$(doit 2 B $C) && + A=$(doit 1 A $B) && + G=$(doit 7 G $B $E) && + H=$(doit 8 H $A $F) +' + +test_expect_success 'merge-base G H' ' + git name-rev $B >expected && + + MB=$(git merge-base G H) && + git name-rev "$MB" >actual.single && + + MB=$(git merge-base --all G H) && + git name-rev "$MB" >actual.all && + + MB=$(git show-branch --merge-base G H) && + git name-rev "$MB" >actual.sb && + + test_cmp expected actual.single && + test_cmp expected actual.all && + test_cmp expected actual.sb +' +test_expect_success 'merge-base/show-branch --independent' ' + git name-rev "$H" >expected1 && + git name-rev "$H" "$G" >expected2 && + + parents=$(git merge-base --independent H) && + git name-rev $parents >actual1.mb && + parents=$(git merge-base --independent A H G) && + git name-rev $parents >actual2.mb && + + parents=$(git show-branch --independent H) && + git name-rev $parents >actual1.sb && + parents=$(git show-branch --independent A H G) && + git name-rev $parents >actual2.sb && + + test_cmp expected1 actual1.mb && + test_cmp expected2 actual2.mb && + test_cmp expected1 actual1.sb && + test_cmp expected2 actual2.sb +' + +test_expect_success 'unsynchronized clocks' ' + # This test is to demonstrate that relying on timestamps in a distributed + # SCM to provide a _consistent_ partial ordering of commits leads to + # insanity. + # + # Relative + # Structure timestamps + # + # PL PR +4 +4 + # / \/ \ / \/ \ + # L2 C2 R2 +3 -1 +3 + # | | | | | | + # L1 C1 R1 +2 -2 +2 + # | | | | | | + # L0 C0 R0 +1 -3 +1 + # \ | / \ | / + # S 0 + # + # The left and right chains of commits can be of any length and complexity as + # long as all of the timestamps are greater than that of S. + + S=$(doit 0 S) && + + C0=$(doit -3 C0 $S) && + C1=$(doit -2 C1 $C0) && + C2=$(doit -1 C2 $C1) && + + L0=$(doit 1 L0 $S) && + L1=$(doit 2 L1 $L0) && + L2=$(doit 3 L2 $L1) && + + R0=$(doit 1 R0 $S) && + R1=$(doit 2 R1 $R0) && + R2=$(doit 3 R2 $R1) && + + PL=$(doit 4 PL $L2 $C2) && + PR=$(doit 4 PR $C2 $R2) + + git name-rev $C2 >expected && + + MB=$(git merge-base PL PR) && + git name-rev "$MB" >actual.single && + + MB=$(git merge-base --all PL PR) && + git name-rev "$MB" >actual.all && + + test_cmp expected actual.single && + test_cmp expected actual.all +' + +test_expect_success '--independent with unsynchronized clocks' ' + IB=$(doit 0 IB) && + I1=$(doit -10 I1 $IB) && + I2=$(doit -9 I2 $I1) && + I3=$(doit -8 I3 $I2) && + I4=$(doit -7 I4 $I3) && + I5=$(doit -6 I5 $I4) && + I6=$(doit -5 I6 $I5) && + I7=$(doit -4 I7 $I6) && + I8=$(doit -3 I8 $I7) && + IH=$(doit -2 IH $I8) && + + echo $IH >expected && + git merge-base --independent IB IH >actual && + test_cmp expected actual +' test_expect_success 'merge-base for octopus-step (setup)' ' - test_tick && git commit --allow-empty -m root && git tag MMR && - test_tick && git commit --allow-empty -m 1 && git tag MM1 && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m A && git tag MMA && + # Another set to demonstrate base between one commit and a merge + # in the documentation. + # + # * C (MMC) * B (MMB) * A (MMA) + # * o * o * o + # * o * o * o + # * o * o * o + # * o | _______/ + # | |/ + # | * 1 (MM1) + # | _______/ + # |/ + # * root (MMR) + + test_commit MMR && + test_commit MM1 && + test_commit MM-o && + test_commit MM-p && + test_commit MM-q && + test_commit MMA && git checkout MM1 && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m B && git tag MMB && + test_commit MM-r && + test_commit MM-s && + test_commit MM-t && + test_commit MMB && git checkout MMR && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m C && git tag MMC + test_commit MM-u && + test_commit MM-v && + test_commit MM-w && + test_commit MM-x && + test_commit MMC ' test_expect_success 'merge-base A B C' ' - MB=$(git merge-base --all MMA MMB MMC) && - MM1=$(git rev-parse --verify MM1) && - test "$MM1" = "$MB" -' + git rev-parse --verify MM1 >expected && + git rev-parse --verify MMR >expected.sb && -test_expect_success 'merge-base A B C using show-branch' ' - MB=$(git show-branch --merge-base MMA MMB MMC) && - MMR=$(git rev-parse --verify MMR) && - test "$MMR" = "$MB" + git merge-base --all MMA MMB MMC >actual && + git merge-base --all --octopus MMA MMB MMC >actual.common && + git show-branch --merge-base MMA MMB MMC >actual.sb && + + test_cmp expected actual && + test_cmp expected.sb actual.common && + test_cmp expected.sb actual.sb ' -test_expect_success 'criss-cross merge-base for octopus-step (setup)' ' +test_expect_success 'criss-cross merge-base for octopus-step' ' git reset --hard MMR && - test_tick && git commit --allow-empty -m 1 && git tag CC1 && + test_commit CC1 && git reset --hard E && - test_tick && git commit --allow-empty -m 2 && git tag CC2 && - test_tick && git merge -s ours CC1 && - test_tick && git commit --allow-empty -m o && - test_tick && git commit --allow-empty -m B && git tag CCB && + test_commit CC2 && + test_tick && + git merge -s ours CC1 && + test_commit CC-o && + test_commit CCB && git reset --hard CC1 && - test_tick && git merge -s ours CC2 && - test_tick && git commit --allow-empty -m A && git tag CCA -' + git merge -s ours CC2 && + test_commit CCA && + + git rev-parse CC1 CC2 >expected && + git merge-base --all CCB CCA^^ CCA^^2 >actual && -test_expect_success 'merge-base B A^^ A^^2' ' - MB0=$(git merge-base --all CCB CCA^^ CCA^^2 | sort) && - MB1=$(git rev-parse CC1 CC2 | sort) && - test "$MB0" = "$MB1" + sort expected >expected.sorted && + sort actual >actual.sorted && + test_cmp expected.sorted actual.sorted ' test_done diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index e71c687f2b..490d397114 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -22,7 +22,7 @@ git commit -m "File: dir"' test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master' -test_expect_failure 'F/D conflict' ' +test_expect_success 'F/D conflict' ' git reset --hard && git checkout master && rm .git/index && diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh index 8a3304fb0b..bd75e0e643 100755 --- a/t/t6031-merge-recursive.sh +++ b/t/t6031-merge-recursive.sh @@ -57,4 +57,35 @@ test_expect_success FILEMODE 'verify executable bit on file' ' test -x file2 ' +test_expect_success 'merging with triple rename across D/F conflict' ' + git reset --hard HEAD && + git checkout -b main && + git rm -rf . && + + echo "just a file" >sub1 && + mkdir -p sub2 && + echo content1 >sub2/file1 && + echo content2 >sub2/file2 && + echo content3 >sub2/file3 && + mkdir simple && + echo base >simple/bar && + git add -A && + test_tick && + git commit -m base && + + git checkout -b other && + echo more >>simple/bar && + test_tick && + git commit -a -m changesimplefile && + + git checkout main && + git rm sub1 && + git mv sub2 sub1 && + test_tick && + git commit -m changefiletodir && + + test_tick && + git merge other +' + test_done diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index cd3190c4a6..dc09513be5 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -48,7 +48,7 @@ test_expect_success 'setup for merge test' ' git tag baseline ' -test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' ' +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' ' git reset --hard && git checkout baseline^0 && git merge -s resolve master && @@ -56,7 +56,7 @@ test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' ' test -f a/b-2/c/d ' -test_expect_failure 'do not lose a/b-2/c/d in merge (recursive)' ' +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' git reset --hard && git checkout baseline^0 && git merge -s recursive master && @@ -64,6 +64,54 @@ test_expect_failure 'do not lose a/b-2/c/d in merge (recursive)' ' test -f a/b-2/c/d ' +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' ' + git reset --hard && + git checkout master^0 && + git merge -s resolve baseline^0 && + test -h a/b && + test -f a/b-2/c/d +' + +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' ' + git reset --hard && + git checkout master^0 && + git merge -s recursive baseline^0 && + test -h a/b && + test -f a/b-2/c/d +' + +test_expect_failure 'do not lose untracked in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + >a/b/c/e && + test_must_fail git merge -s resolve master && + test -f a/b/c/e && + test -f a/b-2/c/d +' + +test_expect_success 'do not lose untracked in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + >a/b/c/e && + test_must_fail git merge -s recursive master && + test -f a/b/c/e && + test -f a/b-2/c/d +' + +test_expect_success 'do not lose modifications in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + echo more content >>a/b/c/d && + test_must_fail git merge -s resolve master +' + +test_expect_success 'do not lose modifications in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + echo more content >>a/b/c/d && + test_must_fail git merge -s recursive master +' + test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' git reset --hard && git checkout start^0 && @@ -74,7 +122,7 @@ test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' git tag test2 ' -test_expect_success 'merge should not have conflicts (resolve)' ' +test_expect_success 'merge should not have D/F conflicts (resolve)' ' git reset --hard && git checkout baseline^0 && git merge -s resolve test2 && @@ -82,7 +130,7 @@ test_expect_success 'merge should not have conflicts (resolve)' ' test -f a/b/c/d ' -test_expect_failure 'merge should not have conflicts (recursive)' ' +test_expect_success 'merge should not have D/F conflicts (recursive)' ' git reset --hard && git checkout baseline^0 && git merge -s recursive test2 && @@ -90,4 +138,12 @@ test_expect_failure 'merge should not have conflicts (recursive)' ' test -f a/b/c/d ' +test_expect_success 'merge should not have F/D conflicts (recursive)' ' + git reset --hard && + git checkout -b foo test2 && + git merge -s recursive baseline^0 && + test -h a/b-2 && + test -f a/b/c/d +' + test_done diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh new file mode 100755 index 0000000000..52d0dc4bb8 --- /dev/null +++ b/t/t6038-merge-text-auto.sh @@ -0,0 +1,189 @@ +#!/bin/sh + +test_description='CRLF merge conflict across text=auto change + +* [master] remove .gitattributes + ! [side] add line from b +-- + + [side] add line from b +* [master] remove .gitattributes +* [master^] add line from a +* [master~2] normalize file +*+ [side^] Initial +' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.autocrlf false && + + echo first line | append_cr >file && + echo first line >control_file && + echo only line >inert_file && + + git add file control_file inert_file && + test_tick && + git commit -m "Initial" && + git tag initial && + git branch side && + + echo "* text=auto" >.gitattributes && + touch file && + git add .gitattributes file && + test_tick && + git commit -m "normalize file" && + + echo same line | append_cr >>file && + echo same line >>control_file && + git add file control_file && + test_tick && + git commit -m "add line from a" && + git tag a && + + git rm .gitattributes && + rm file && + git checkout file && + test_tick && + git commit -m "remove .gitattributes" && + git tag c && + + git checkout side && + echo same line | append_cr >>file && + echo same line >>control_file && + git add file control_file && + test_tick && + git commit -m "add line from b" && + git tag b && + + git checkout master +' + +test_expect_success 'set up fuzz_conflict() helper' ' + fuzz_conflict() { + sed -e "s/^\([<>=]......\) .*/\1/" "$@" + } +' + +test_expect_success 'Merge after setting text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard a && + git merge b && + test_cmp expected file +' + +test_expect_success 'Merge addition of text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard b && + git merge a && + test_cmp expected file +' + +test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' + q_to_cr <<-\EOF >expected && + <<<<<<< + first line + same line + ======= + first lineQ + same lineQ + >>>>>>> + EOF + + git config merge.renormalize false && + rm -f .gitattributes && + git reset --hard a && + test_must_fail git merge b && + fuzz_conflict file >file.fuzzy && + test_cmp expected file.fuzzy +' + +test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' ' + q_to_cr <<-\EOF >expected && + <<<<<<< + first lineQ + same lineQ + ======= + first line + same line + >>>>>>> + EOF + + git config merge.renormalize false && + rm -f .gitattributes && + git reset --hard b && + test_must_fail git merge a && + fuzz_conflict file >file.fuzzy && + test_cmp expected file.fuzzy +' + +test_expect_failure 'checkout -m after setting text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard initial && + git checkout a -- . && + git checkout -m b && + test_cmp expected file +' + +test_expect_failure 'checkout -m addition of text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes file && + git reset --hard initial && + git checkout b -- . && + git checkout -m a && + test_cmp expected file +' + +test_expect_failure 'cherry-pick patch from after text=auto was added' ' + append_cr <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + git reset --hard b && + test_must_fail git cherry-pick a >err 2>&1 && + grep "[Nn]othing added" err && + test_cmp expected file +' + +test_expect_success 'Test delete/normalize conflict' ' + git checkout -f side && + git rm -fr . && + rm -f .gitattributes && + git reset --hard initial && + git rm file && + git commit -m "remove file" && + git checkout master && + git reset --hard a^ && + git merge side +' + +test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 42f8ece097..71f6cad3c2 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -70,14 +70,13 @@ test_expect_success setup ' i=$(($i+1)) done && - git show-branch -' + git show-branch && -cat >expected <<\EOF -Merge branch 'left' -EOF + apos="'\''" +' -test_expect_success 'merge-msg test #1' ' +test_expect_success 'message for merging local branch' ' + echo "Merge branch ${apos}left${apos}" >expected && git checkout master && git fetch . left && @@ -86,11 +85,8 @@ test_expect_success 'merge-msg test #1' ' test_cmp expected actual ' -cat >expected <<EOF -Merge branch 'left' of $(pwd) -EOF - -test_expect_success 'merge-msg test #2' ' +test_expect_success 'message for merging external branch' ' + echo "Merge branch ${apos}left${apos} of $(pwd)" >expected && git checkout master && git fetch "$(pwd)" left && @@ -99,139 +95,140 @@ test_expect_success 'merge-msg test #2' ' test_cmp expected actual ' -cat >expected <<\EOF -Merge branch 'left' - -* left: - Left #5 - Left #4 - Left #3 - Common #2 - Common #1 -EOF +test_expect_success '[merge] summary/log configuration' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} -test_expect_success 'merge-msg test #3-1' ' + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF - git config --unset-all merge.log - git config --unset-all merge.summary git config merge.log true && + test_might_fail git config --unset-all merge.summary && git checkout master && test_tick && git fetch . left && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual -' - -test_expect_success 'merge-msg test #3-2' ' + git fmt-merge-msg <.git/FETCH_HEAD >actual1 && - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary true && git checkout master && test_tick && git fetch . left && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -Merge branches 'left' and 'right' + git fmt-merge-msg <.git/FETCH_HEAD >actual2 && -* left: - Left #5 - Left #4 - Left #3 - Common #2 - Common #1 + test_cmp expected actual1 && + test_cmp expected actual2 +' -* right: - Right #5 - Right #4 - Right #3 - Common #2 - Common #1 -EOF +test_expect_success 'fmt-merge-msg -m' ' + echo "Sync with left" >expected && + cat >expected.log <<-EOF && + Sync with left + + * ${apos}left${apos} of $(pwd): + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + test_might_fail git config --unset merge.log && + test_might_fail git config --unset merge.summary && + git checkout master && + git fetch "$(pwd)" left && + git fmt-merge-msg -m "Sync with left" <.git/FETCH_HEAD >actual && + git fmt-merge-msg --log -m "Sync with left" \ + <.git/FETCH_HEAD >actual.log && + git config merge.log true && + git fmt-merge-msg -m "Sync with left" \ + <.git/FETCH_HEAD >actual.log-config && + git fmt-merge-msg --no-log -m "Sync with left" \ + <.git/FETCH_HEAD >actual.nolog && + + test_cmp expected actual && + test_cmp expected.log actual.log && + test_cmp expected.log actual.log-config && + test_cmp expected actual.nolog +' -test_expect_success 'merge-msg test #4-1' ' +test_expect_success 'setup: expected shortlog for two branches' ' + cat >expected <<-EOF + Merge branches ${apos}left${apos} and ${apos}right${apos} + + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + + * right: + Right #5 + Right #4 + Right #3 + Common #2 + Common #1 + EOF +' - git config --unset-all merge.log - git config --unset-all merge.summary +test_expect_success 'shortlog for two branches' ' git config merge.log true && - + test_might_fail git config --unset-all merge.summary && git checkout master && test_tick && git fetch . left right && + git fmt-merge-msg <.git/FETCH_HEAD >actual1 && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual -' - -test_expect_success 'merge-msg test #4-2' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary true && - git checkout master && test_tick && git fetch . left right && + git fmt-merge-msg <.git/FETCH_HEAD >actual2 && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual -' - -test_expect_success 'merge-msg test #5-1' ' - - git config --unset-all merge.log - git config --unset-all merge.summary git config merge.log yes && - + test_might_fail git config --unset-all merge.summary && git checkout master && test_tick && git fetch . left right && + git fmt-merge-msg <.git/FETCH_HEAD >actual3 && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual -' - -test_expect_success 'merge-msg test #5-2' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary yes && - git checkout master && test_tick && git fetch . left right && + git fmt-merge-msg <.git/FETCH_HEAD >actual4 && - git fmt-merge-msg <.git/FETCH_HEAD >actual && - test_cmp expected actual + test_cmp expected actual1 && + test_cmp expected actual2 && + test_cmp expected actual3 && + test_cmp expected actual4 ' test_expect_success 'merge-msg -F' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary yes && - git checkout master && test_tick && git fetch . left right && - git fmt-merge-msg -F .git/FETCH_HEAD >actual && test_cmp expected actual ' test_expect_success 'merge-msg -F in subdirectory' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary yes && - git checkout master && test_tick && git fetch . left right && @@ -245,11 +242,11 @@ test_expect_success 'merge-msg -F in subdirectory' ' ' test_expect_success 'merge-msg with nothing to merge' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + test_might_fail git config --unset-all merge.log && git config merge.summary yes && + >empty && + ( cd remote && git checkout -b unrelated && @@ -258,22 +255,20 @@ test_expect_success 'merge-msg with nothing to merge' ' git fmt-merge-msg <.git/FETCH_HEAD >../actual ) && - test_cmp /dev/null actual + test_cmp empty actual ' -cat >expected <<\EOF -Merge tag 'tag-r3' - -* tag 'tag-r3': - Right #3 - Common #2 - Common #1 -EOF - test_expect_success 'merge-msg tag' ' + cat >expected <<-EOF && + Merge tag ${apos}tag-r3${apos} - git config --unset-all merge.log - git config --unset-all merge.summary + * tag ${apos}tag-r3${apos}: + Right #3 + Common #2 + Common #1 + EOF + + test_might_fail git config --unset-all merge.log && git config merge.summary yes && git checkout master && @@ -284,26 +279,24 @@ test_expect_success 'merge-msg tag' ' test_cmp expected actual ' -cat >expected <<\EOF -Merge tags 'tag-r3' and 'tag-l5' - -* tag 'tag-r3': - Right #3 - Common #2 - Common #1 - -* tag 'tag-l5': - Left #5 - Left #4 - Left #3 - Common #2 - Common #1 -EOF - test_expect_success 'merge-msg two tags' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + cat >expected <<-EOF && + Merge tags ${apos}tag-r3${apos} and ${apos}tag-l5${apos} + + * tag ${apos}tag-r3${apos}: + Right #3 + Common #2 + Common #1 + + * tag ${apos}tag-l5${apos}: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + test_might_fail git config --unset-all merge.log && git config merge.summary yes && git checkout master && @@ -314,26 +307,24 @@ test_expect_success 'merge-msg two tags' ' test_cmp expected actual ' -cat >expected <<\EOF -Merge branch 'left', tag 'tag-r3' - -* tag 'tag-r3': - Right #3 - Common #2 - Common #1 - -* left: - Left #5 - Left #4 - Left #3 - Common #2 - Common #1 -EOF - test_expect_success 'merge-msg tag and branch' ' - - git config --unset-all merge.log - git config --unset-all merge.summary + cat >expected <<-EOF && + Merge branch ${apos}left${apos}, tag ${apos}tag-r3${apos} + + * tag ${apos}tag-r3${apos}: + Right #3 + Common #2 + Common #1 + + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + test_might_fail git config --unset-all merge.log && git config merge.summary yes && git checkout master && @@ -344,26 +335,27 @@ test_expect_success 'merge-msg tag and branch' ' test_cmp expected actual ' -cat >expected <<\EOF -Merge branch 'long' - -* long: (35 commits) -EOF - test_expect_success 'merge-msg lots of commits' ' + { + cat <<-EOF && + Merge branch ${apos}long${apos} + + * long: (35 commits) + EOF + + i=29 && + while test $i -gt 9 + do + echo " $i" && + i=$(($i-1)) + done && + echo " ..." + } >expected && git checkout master && test_tick && git fetch . long && - i=29 && - while test $i -gt 9 - do - echo " $i" && - i=$(($i-1)) - done >>expected && - echo " ..." >>expected - git fmt-merge-msg <.git/FETCH_HEAD >actual && test_cmp expected actual ' diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 71d3ceff8f..fb744e3c4a 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -58,6 +58,21 @@ test_expect_success TTY 'some commands use a pager' ' test -e paginated.out ' +test_expect_failure TTY 'pager runs from subdir' ' + echo subdir/paginated.out >expected && + mkdir -p subdir && + rm -f paginated.out subdir/paginated.out && + ( + cd subdir && + test_terminal git log + ) && + { + ls paginated.out subdir/paginated.out || + : + } >actual && + test_cmp expected actual +' + test_expect_success TTY 'some commands do not use a pager' ' rm -f paginated.out || cleanup_fail && @@ -106,6 +121,45 @@ test_expect_success TTY 'no pager with --no-pager' ' ! test -e paginated.out ' +test_expect_success TTY 'configuration can disable pager' ' + rm -f paginated.out && + test_might_fail git config --unset 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_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_terminal git config --list && + test -e paginated.out +' + +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" && + + git bundle create test.bundle --all && + rm -f paginated.out subdir/paginated.out && + ( + cd subdir && + test_terminal git bundle unbundle ../test.bundle + ) && + { + test -e paginated.out || + test -e subdir/paginated.out + } +' + # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { @@ -369,4 +423,16 @@ test_GIT_PAGER_overrides expect_success test_must_fail 'git -p' test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense' +test_pager_choices 'git shortlog' +test_expect_success 'setup: configure shortlog not to paginate' ' + git config pager.shortlog false +' +test_doesnt_paginate expect_success 'git shortlog' +test_no_local_config_subdir expect_success 'git shortlog' +test_default_pager expect_success 'git -p shortlog' +test_core_pager_subdir expect_success 'git -p shortlog' + +test_core_pager_subdir expect_success test_must_fail \ + 'git -p apply </dev/null' + test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index bade2179b1..02522f9627 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -58,6 +58,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' ' (cd super-clone/submodule && git checkout master && git pull + ) && + (cd super-clone && + test -d "$(git config submodule.submodule.url)" ) ' diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 6ec559db0f..7e2e258950 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -67,7 +67,7 @@ test_expect_success setup ' # b in the main repository. test_expect_success 'setup for merge search' ' mkdir merge-search && - cd merge-search && + (cd merge-search && git init && mkdir sub && (cd sub && @@ -101,8 +101,7 @@ test_expect_success 'setup for merge search' ' git checkout -b sub-d sub-b && git merge sub-c) && git commit -a -m "d" && - git branch test b && - cd .. + git branch test b) ' test_expect_success 'merge with one side as a fast-forward of the other' ' @@ -126,7 +125,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' ' test_expect_success 'merging should fail for ambiguous common parent' ' - cd merge-search && + (cd merge-search && git checkout -b test-ambiguous b && (cd sub && git checkout -b ambiguous sub-b && @@ -136,8 +135,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' test_must_fail git merge c 2> actual && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && - git reset --hard && - cd .. + git reset --hard) ' # in a situation like this @@ -158,7 +156,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' # commits (sub-a) does not descend from the submodule merge-base (sub-b). # test_expect_success 'merging should fail for changes that are backwards' ' - cd merge-search && + (cd merge-search && git checkout -b bb a && (cd sub && git checkout sub-b) && @@ -175,16 +173,13 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f && - cd .. + test_must_fail git merge f) ' test_expect_success 'merging with a modify/modify conflict between merge bases' ' - git reset --hard HEAD && git checkout -b test2 c && git merge d - ' test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 1382a8e58a..bfb4975e94 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -25,7 +25,7 @@ test_expect_success 'setup a submodule tree' ' echo file > file && git add file && test_tick && - git commit -m upstream + git commit -m upstream && git clone . super && git clone super submodule && git clone super rebasing && diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index db9365b645..905a8baae9 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -16,7 +16,7 @@ test_expect_success 'setup a submodule tree' ' echo file > file && git add file && test_tick && - git commit -m upstream + git commit -m upstream && git clone . super && git clone super submodule && ( @@ -30,7 +30,7 @@ test_expect_success 'setup a submodule tree' ' submodule.sub2 submodule.foo2 && git config -f .gitmodules --rename-section \ submodule.sub3 submodule.foo3 && - git add .gitmodules + git add .gitmodules && test_tick && git commit -m "submodules" && git submodule init sub1 && diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index cde8390c1b..b4f40e4c3a 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -5,189 +5,103 @@ test_description='git merge -Testing basic merge operations/option parsing.' +Testing basic merge operations/option parsing. + +! [c0] commit 0 + ! [c1] commit 1 + ! [c2] commit 2 + ! [c3] commit 3 + ! [c4] c4 + ! [c5] c5 + ! [c6] c6 + * [master] Merge commit 'c1' +-------- + - [master] Merge commit 'c1' + + * [c1] commit 1 + + [c6] c6 + + [c5] c5 + ++ [c4] c4 + ++++ [c3] commit 3 + + [c2] commit 2 ++++++++* [c0] commit 0 +' . ./test-lib.sh -cat >file <<EOF -1 -2 -3 -4 -5 -6 -7 -8 -9 -EOF - -cat >file.1 <<EOF -1 X -2 -3 -4 -5 -6 -7 -8 -9 -EOF - -cat >file.5 <<EOF -1 -2 -3 -4 -5 X -6 -7 -8 -9 -EOF - -cat >file.9 <<EOF -1 -2 -3 -4 -5 -6 -7 -8 -9 X -EOF - -cat >result.1 <<EOF -1 X -2 -3 -4 -5 -6 -7 -8 -9 -EOF - -cat >result.1-5 <<EOF -1 X -2 -3 -4 -5 X -6 -7 -8 -9 -EOF - -cat >result.1-5-9 <<EOF -1 X -2 -3 -4 -5 X -6 -7 -8 -9 X -EOF - -create_merge_msgs() { - echo "Merge commit 'c2'" >msg.1-5 && - echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && - echo "Squashed commit of the following:" >squash.1 && - echo >>squash.1 && - git log --no-merges ^HEAD c1 >>squash.1 && - echo "Squashed commit of the following:" >squash.1-5 && - echo >>squash.1-5 && - git log --no-merges ^HEAD c2 >>squash.1-5 && - echo "Squashed commit of the following:" >squash.1-5-9 && - echo >>squash.1-5-9 && - git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 && - echo > msg.nolog && - echo "* commit 'c3':" >msg.log && - echo " commit 3" >>msg.log && - echo >>msg.log -} - -verify_diff() { - if ! test_cmp "$1" "$2" - then - echo "$3" - false - fi -} - -verify_merge() { - verify_diff "$2" "$1" "[OOPS] bad merge result" && - if test $(git ls-files -u | wc -l) -gt 0 - then - echo "[OOPS] unmerged files" - false - fi && - if test_must_fail git diff --exit-code - then - echo "[OOPS] working tree != index" - false - fi && - if test -n "$3" - then - git show -s --pretty=format:%s HEAD >msg.act && - verify_diff "$3" msg.act "[OOPS] bad merge message" - fi -} - -verify_head() { - if test "$1" != "$(git rev-parse HEAD)" - then - echo "[OOPS] HEAD != $1" - false - fi -} - -verify_parents() { - i=1 - while test $# -gt 0 - do - if test "$1" != "$(git rev-parse HEAD^$i)" +test_expect_success 'set up test data and helpers' ' + printf "%s\n" 1 2 3 4 5 6 7 8 9 >file && + printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >file.1 && + printf "%s\n" 1 2 3 4 "5 X" 6 7 8 9 >file.5 && + printf "%s\n" 1 2 3 4 5 6 7 8 "9 X" >file.9 && + printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >result.1 && + printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 9 >result.1-5 && + printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 "9 X" >result.1-5-9 && + + create_merge_msgs() { + echo "Merge commit '\''c2'\''" >msg.1-5 && + echo "Merge commit '\''c2'\''; commit '\''c3'\''" >msg.1-5-9 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c1 + } >squash.1 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 + } >squash.1-5 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 c3 + } >squash.1-5-9 && + echo >msg.nolog && + { + echo "* commit '\''c3'\'':" && + echo " commit 3" && + echo + } >msg.log + } && + + verify_merge() { + test_cmp "$2" "$1" && + git update-index --refresh && + git diff --exit-code && + if test -n "$3" then - echo "[OOPS] HEAD^$i != $1" - return 1 + git show -s --pretty=format:%s HEAD >msg.act && + test_cmp "$3" msg.act fi - i=$(expr $i + 1) - shift - done -} - -verify_mergeheads() { - i=1 - if ! test -f .git/MERGE_HEAD - then - echo "[OOPS] MERGE_HEAD is missing" - false - fi && - while test $# -gt 0 - do - head=$(head -n $i .git/MERGE_HEAD | sed -ne \$p) - if test "$1" != "$head" - then - echo "[OOPS] MERGE_HEAD $i != $1" + } && + + verify_head() { + echo "$1" >head.expected && + git rev-parse HEAD >head.actual && + test_cmp head.expected head.actual + } && + + verify_parents() { + printf "%s\n" "$@" >parents.expected && + >parents.actual && + i=1 && + while test $i -le $# + do + git rev-parse HEAD^$i >>parents.actual && + i=$(expr $i + 1) || return 1 - fi - i=$(expr $i + 1) - shift - done -} + done && + test_cmp parents.expected parents.actual + } && -verify_no_mergehead() { - if test -f .git/MERGE_HEAD - then - echo "[OOPS] MERGE_HEAD exists" - false - fi -} + verify_mergeheads() { + printf "%s\n" "$@" >mergehead.expected && + test_cmp mergehead.expected .git/MERGE_HEAD + } && + verify_no_mergehead() { + ! test -e .git/MERGE_HEAD + } +' test_expect_success 'setup' ' git add file && @@ -219,7 +133,7 @@ test_expect_success 'setup' ' create_merge_msgs ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'test option parsing' ' test_must_fail git merge -$ c1 && @@ -235,13 +149,19 @@ test_expect_success 'reject non-strategy with a git-merge-foo name' ' ' test_expect_success 'merge c0 with c1' ' + echo "OBJID HEAD@{0}: merge c1: Fast-forward" >reflog.expected && + git reset --hard c0 && git merge c1 && verify_merge file result.1 && - verify_head "$c1" + verify_head "$c1" && + + git reflog -1 >reflog.actual && + sed "s/$_x05[0-9a-f]*/OBJID/g" reflog.actual >reflog.fuzzy && + test_cmp reflog.expected reflog.fuzzy ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 with --ff-only' ' git reset --hard c0 && @@ -251,7 +171,28 @@ test_expect_success 'merge c0 with c1 with --ff-only' ' verify_head "$c1" ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' + +test_expect_success 'merge from unborn branch' ' + git checkout -f master && + test_might_fail git branch -D kid && + + echo "OBJID HEAD@{0}: initial pull" >reflog.expected && + + git checkout --orphan kid && + test_when_finished "git checkout -f master" && + git rm -fr . && + test_tick && + git merge --ff-only c1 && + verify_merge file result.1 && + verify_head "$c1" && + + git reflog -1 >reflog.actual && + sed "s/$_x05[0-9a-f][0-9a-f]/OBJID/g" reflog.actual >reflog.fuzzy && + test_cmp reflog.expected reflog.fuzzy +' + +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2' ' git reset --hard c1 && @@ -261,7 +202,7 @@ test_expect_success 'merge c1 with c2' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 and c3' ' git reset --hard c1 && @@ -271,7 +212,7 @@ test_expect_success 'merge c1 with c2 and c3' ' verify_parents $c1 $c2 $c3 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'failing merges with --ff-only' ' git reset --hard c1 && @@ -288,7 +229,7 @@ test_expect_success 'merge c0 with c1 (no-commit)' ' verify_head $c1 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (no-commit)' ' git reset --hard c1 && @@ -298,7 +239,7 @@ test_expect_success 'merge c1 with c2 (no-commit)' ' verify_mergeheads $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 and c3 (no-commit)' ' git reset --hard c1 && @@ -308,7 +249,7 @@ test_expect_success 'merge c1 with c2 and c3 (no-commit)' ' verify_mergeheads $c2 $c3 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (squash)' ' git reset --hard c0 && @@ -316,10 +257,10 @@ test_expect_success 'merge c0 with c1 (squash)' ' verify_merge file result.1 && verify_head $c0 && verify_no_mergehead && - verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" + test_cmp squash.1 .git/SQUASH_MSG ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (squash, ff-only)' ' git reset --hard c0 && @@ -327,10 +268,10 @@ test_expect_success 'merge c0 with c1 (squash, ff-only)' ' verify_merge file result.1 && verify_head $c0 && verify_no_mergehead && - verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" + test_cmp squash.1 .git/SQUASH_MSG ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (squash)' ' git reset --hard c1 && @@ -338,17 +279,17 @@ test_expect_success 'merge c1 with c2 (squash)' ' verify_merge file result.1-5 && verify_head $c1 && verify_no_mergehead && - verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message" + test_cmp squash.1-5 .git/SQUASH_MSG ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' ' git reset --hard c1 && test_must_fail git merge --squash --ff-only c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 and c3 (squash)' ' git reset --hard c1 && @@ -356,10 +297,10 @@ test_expect_success 'merge c1 with c2 and c3 (squash)' ' verify_merge file result.1-5-9 && verify_head $c1 && verify_no_mergehead && - verify_diff squash.1-5-9 .git/SQUASH_MSG "[OOPS] bad squash message" + test_cmp squash.1-5-9 .git/SQUASH_MSG ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (no-commit in config)' ' git reset --hard c1 && @@ -370,7 +311,7 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' ' verify_mergeheads $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (squash in config)' ' git reset --hard c1 && @@ -379,10 +320,10 @@ test_expect_success 'merge c1 with c2 (squash in config)' ' verify_merge file result.1-5 && verify_head $c1 && verify_no_mergehead && - verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message" + test_cmp squash.1-5 .git/SQUASH_MSG ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option -n with --summary' ' git reset --hard c1 && @@ -412,7 +353,7 @@ test_expect_success 'override config option -n with --stat' ' fi ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option --stat' ' git reset --hard c1 && @@ -428,7 +369,7 @@ test_expect_success 'override config option --stat' ' fi ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --no-commit)' ' git reset --hard c1 && @@ -439,7 +380,7 @@ test_expect_success 'merge c1 with c2 (override --no-commit)' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --squash)' ' git reset --hard c1 && @@ -450,7 +391,7 @@ test_expect_success 'merge c1 with c2 (override --squash)' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (no-ff)' ' git reset --hard c0 && @@ -461,7 +402,7 @@ test_expect_success 'merge c0 with c1 (no-ff)' ' verify_parents $c0 $c1 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'combining --squash and --no-ff is refused' ' test_must_fail git merge --squash --no-ff c1 && @@ -485,20 +426,20 @@ test_expect_success 'merge log message' ' git reset --hard c0 && git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && - verify_diff msg.nolog msg.act "[OOPS] bad merge log message" && + test_cmp msg.nolog msg.act && git merge --log c3 && git show -s --pretty=format:%b HEAD >msg.act && - verify_diff msg.log msg.act "[OOPS] bad merge log message" && + test_cmp msg.log msg.act && git reset --hard HEAD^ && git config merge.log yes && git merge c3 && git show -s --pretty=format:%b HEAD >msg.act && - verify_diff msg.log msg.act "[OOPS] bad merge log message" + test_cmp msg.log msg.act ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && @@ -509,7 +450,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && @@ -520,7 +461,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c1 and c2' ' git reset --hard c1 && @@ -531,7 +472,7 @@ test_expect_success 'merge c1 with c1 and c2' ' verify_parents $c1 $c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge fast-forward in a dirty tree' ' git reset --hard c0 && @@ -541,16 +482,16 @@ test_expect_success 'merge fast-forward in a dirty tree' ' git merge c2 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'in-index merge' ' git reset --hard c0 && - git merge --no-ff -s resolve c1 > out && + git merge --no-ff -s resolve c1 >out && grep "Wonderful." out && verify_parents $c0 $c1 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'refresh the index before merging' ' git reset --hard c1 && @@ -558,31 +499,39 @@ test_expect_success 'refresh the index before merging' ' git merge c3 ' -cat >expected <<EOF -Merge branch 'c5' (early part) +cat >expected.branch <<\EOF +Merge branch 'c5-branch' (early part) +EOF +cat >expected.tag <<\EOF +Merge commit 'c5~1' EOF test_expect_success 'merge early part of c2' ' git reset --hard c3 && - echo c4 > c4.c && + echo c4 >c4.c && git add c4.c && git commit -m c4 && git tag c4 && - echo c5 > c5.c && + echo c5 >c5.c && git add c5.c && git commit -m c5 && git tag c5 && git reset --hard c3 && - echo c6 > c6.c && + echo c6 >c6.c && git add c6.c && git commit -m c6 && git tag c6 && + git branch -f c5-branch c5 && + git merge c5-branch~1 && + git show -s --pretty=format:%s HEAD >actual.branch && + git reset --keep HEAD^ && git merge c5~1 && - git show -s --pretty=format:%s HEAD > actual && - test_cmp actual expected + git show -s --pretty=format:%s HEAD >actual.tag && + test_cmp expected.branch actual.branch && + test_cmp expected.tag actual.tag ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge --no-ff --no-commit && commit' ' git reset --hard c0 && @@ -591,13 +540,13 @@ test_expect_success 'merge --no-ff --no-commit && commit' ' verify_parents $c0 $c1 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'amending no-ff merge commit' ' EDITOR=: git commit --amend && verify_parents $c0 $c1 ' -test_debug 'gitk --all' +test_debug 'git log --graph --decorate --oneline --all' test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index f5a7bf47e9..3bd74042ef 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -68,22 +68,24 @@ test_expect_success 'mergetool crlf' ' ' test_expect_success 'mergetool in subdir' ' - git checkout -b test3 branch1 - cd subdir && ( - test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && - test "$(cat file3)" = "master new sub") && - cd .. + git checkout -b test3 branch1 && + ( + cd subdir && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && + test "$(cat file3)" = "master new sub" + ) ' test_expect_success 'mergetool on file in parent dir' ' - cd subdir && ( - ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && - ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && - test "$(cat ../file1)" = "master updated" && - test "$(cat ../file2)" = "master new" && - git commit -m "branch1 resolved with mergetool - subdir") && - cd .. + ( + cd subdir && + ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + git commit -m "branch1 resolved with mergetool - subdir" + ) ' test_expect_success 'mergetool skips autoresolved' ' @@ -96,16 +98,17 @@ test_expect_success 'mergetool skips autoresolved' ' ' test_expect_success 'mergetool merges all from subdir' ' - cd subdir && ( - git config rerere.enabled false && - test_must_fail git merge master && - git mergetool --no-prompt && - test "$(cat ../file1)" = "master updated" && - test "$(cat ../file2)" = "master new" && - test "$(cat file3)" = "master new sub" && - git add ../file1 ../file2 file3 && - git commit -m "branch2 resolved by mergetool from subdir") && - cd .. + ( + cd subdir && + git config rerere.enabled false && + test_must_fail git merge master && + git mergetool --no-prompt && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + git add ../file1 ../file2 file3 && + git commit -m "branch2 resolved by mergetool from subdir" + ) ' test_done diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh new file mode 100755 index 0000000000..a713dfc50b --- /dev/null +++ b/t/t9010-svn-fe.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='check svn dumpfile importer' + +. ./lib-git-svn.sh + +test_dump() { + label=$1 + dump=$2 + test_expect_success "$dump" ' + svnadmin create "$label-svn" && + svnadmin load "$label-svn" < "$TEST_DIRECTORY/$dump" && + svn_cmd export "file://$PWD/$label-svn" "$label-svnco" && + git init "$label-git" && + test-svn-fe "$TEST_DIRECTORY/$dump" >"$label.fe" && + ( + cd "$label-git" && + git fast-import < ../"$label.fe" + ) && + ( + cd "$label-svnco" && + git init && + git add . && + git fetch "../$label-git" master && + git diff --exit-code FETCH_HEAD + ) + ' +} + +test_dump simple t9135/svn.dump + +test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index d43f37ccaf..8c8e679468 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -355,6 +355,20 @@ test_expect_failure 'no exact-ref revisions included' ' ) ' +test_expect_success 'path limiting with import-marks does not lose unmodified files' ' + git checkout -b simple marks~2 && + git fast-export --export-marks=marks simple -- file > /dev/null && + echo more content >> file && + test_tick && + git commit -mnext file && + git fast-export --import-marks=marks simple -- file file0 | grep file0 +' + +test_expect_success 'full-tree re-shows unmodified files' ' + git checkout -f simple && + test $(git fast-export --full-tree simple | grep -c file0) -eq 3 +' + test_expect_success 'set-up a few more tags for tag export tests' ' git checkout -f master && HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` && @@ -376,4 +390,28 @@ test_expect_success 'tree_tag-obj' 'git fast-export tree_tag-obj' test_expect_success 'tag-obj_tag' 'git fast-export tag-obj_tag' test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj' +test_expect_success SYMLINKS 'directory becomes symlink' ' + git init dirtosymlink && + git init result && + ( + cd dirtosymlink && + mkdir foo && + mkdir bar && + echo hello > foo/world && + echo hello > bar/world && + git add foo/world bar/world && + git commit -q -mone && + git rm -r foo && + ln -s bar foo && + git add foo && + git commit -q -mtwo + ) && + ( + cd dirtosymlink && + git fast-export master -- foo | + (cd ../result && git fast-import --quiet) + ) && + (cd result && git show master:foo) +' + test_done diff --git a/test-line-buffer.c b/test-line-buffer.c new file mode 100644 index 0000000000..c11bf7f967 --- /dev/null +++ b/test-line-buffer.c @@ -0,0 +1,46 @@ +/* + * test-line-buffer.c: code to exercise the svn importer's input helper + * + * Input format: + * number NL + * (number bytes) NL + * number NL + * ... + */ + +#include "git-compat-util.h" +#include "vcs-svn/line_buffer.h" + +static uint32_t strtouint32(const char *s) +{ + char *end; + uintmax_t n = strtoumax(s, &end, 10); + if (*s == '\0' || *end != '\0') + die("invalid count: %s", s); + return (uint32_t) n; +} + +int main(int argc, char *argv[]) +{ + char *s; + + if (argc != 1) + usage("test-line-buffer < input.txt"); + if (buffer_init(NULL)) + die_errno("open error"); + while ((s = buffer_read_line())) { + s = buffer_read_string(strtouint32(s)); + fputs(s, stdout); + fputc('\n', stdout); + buffer_skip_bytes(1); + if (!(s = buffer_read_line())) + break; + buffer_copy_bytes(strtouint32(s) + 1); + } + if (buffer_deinit()) + die("input error"); + if (ferror(stdout)) + die("output error"); + buffer_reset(); + return 0; +} diff --git a/test-obj-pool.c b/test-obj-pool.c new file mode 100644 index 0000000000..5018863ef5 --- /dev/null +++ b/test-obj-pool.c @@ -0,0 +1,116 @@ +/* + * test-obj-pool.c: code to exercise the svn importer's object pool + */ + +#include "cache.h" +#include "vcs-svn/obj_pool.h" + +enum pool { POOL_ONE, POOL_TWO }; +obj_pool_gen(one, int, 1) +obj_pool_gen(two, int, 4096) + +static uint32_t strtouint32(const char *s) +{ + char *end; + uintmax_t n = strtoumax(s, &end, 10); + if (*s == '\0' || (*end != '\n' && *end != '\0')) + die("invalid offset: %s", s); + return (uint32_t) n; +} + +static void handle_command(const char *command, enum pool pool, const char *arg) +{ + switch (*command) { + case 'a': + if (!prefixcmp(command, "alloc ")) { + uint32_t n = strtouint32(arg); + printf("%"PRIu32"\n", + pool == POOL_ONE ? + one_alloc(n) : two_alloc(n)); + return; + } + case 'c': + if (!prefixcmp(command, "commit ")) { + pool == POOL_ONE ? one_commit() : two_commit(); + return; + } + if (!prefixcmp(command, "committed ")) { + printf("%"PRIu32"\n", + pool == POOL_ONE ? + one_pool.committed : two_pool.committed); + return; + } + case 'f': + if (!prefixcmp(command, "free ")) { + uint32_t n = strtouint32(arg); + pool == POOL_ONE ? one_free(n) : two_free(n); + return; + } + case 'n': + if (!prefixcmp(command, "null ")) { + printf("%"PRIu32"\n", + pool == POOL_ONE ? + one_offset(NULL) : two_offset(NULL)); + return; + } + case 'o': + if (!prefixcmp(command, "offset ")) { + uint32_t n = strtouint32(arg); + printf("%"PRIu32"\n", + pool == POOL_ONE ? + one_offset(one_pointer(n)) : + two_offset(two_pointer(n))); + return; + } + case 'r': + if (!prefixcmp(command, "reset ")) { + pool == POOL_ONE ? one_reset() : two_reset(); + return; + } + case 's': + if (!prefixcmp(command, "set ")) { + uint32_t n = strtouint32(arg); + if (pool == POOL_ONE) + *one_pointer(n) = 1; + else + *two_pointer(n) = 1; + return; + } + case 't': + if (!prefixcmp(command, "test ")) { + uint32_t n = strtouint32(arg); + printf("%d\n", pool == POOL_ONE ? + *one_pointer(n) : *two_pointer(n)); + return; + } + default: + die("unrecognized command: %s", command); + } +} + +static void handle_line(const char *line) +{ + const char *arg = strchr(line, ' '); + enum pool pool; + + if (arg && !prefixcmp(arg + 1, "one")) + pool = POOL_ONE; + else if (arg && !prefixcmp(arg + 1, "two")) + pool = POOL_TWO; + else + die("no pool specified: %s", line); + + handle_command(line, pool, arg + strlen("one ")); +} + +int main(int argc, char *argv[]) +{ + struct strbuf sb = STRBUF_INIT; + if (argc != 1) + usage("test-obj-str < script"); + + while (strbuf_getline(&sb, stdin, '\n') != EOF) + handle_line(sb.buf); + strbuf_release(&sb); + return 0; +} diff --git a/test-string-pool.c b/test-string-pool.c new file mode 100644 index 0000000000..c5782e6bce --- /dev/null +++ b/test-string-pool.c @@ -0,0 +1,31 @@ +/* + * test-string-pool.c: code to exercise the svn importer's string pool + */ + +#include "git-compat-util.h" +#include "vcs-svn/string_pool.h" + +int main(int argc, char *argv[]) +{ + const uint32_t unequal = pool_intern("does not equal"); + const uint32_t equal = pool_intern("equals"); + uint32_t buf[3]; + uint32_t n; + + if (argc != 2) + usage("test-string-pool <string>,<string>"); + + n = pool_tok_seq(3, buf, ",-", argv[1]); + if (n >= 3) + die("too many strings"); + if (n <= 1) + die("too few strings"); + + buf[2] = buf[1]; + buf[1] = (buf[0] == buf[2]) ? equal : unequal; + pool_print_seq(3, buf, ' ', stdout); + fputc('\n', stdout); + + pool_reset(); + return 0; +} diff --git a/test-svn-fe.c b/test-svn-fe.c new file mode 100644 index 0000000000..77cf78abcf --- /dev/null +++ b/test-svn-fe.c @@ -0,0 +1,17 @@ +/* + * test-svn-fe: Code to exercise the svn import lib + */ + +#include "git-compat-util.h" +#include "vcs-svn/svndump.h" + +int main(int argc, char *argv[]) +{ + if (argc != 2) + usage("test-svn-fe <file>"); + svndump_init(argv[1]); + svndump_read(NULL); + svndump_deinit(); + svndump_reset(); + return 0; +} diff --git a/test-treap.c b/test-treap.c new file mode 100644 index 0000000000..cdba5111e1 --- /dev/null +++ b/test-treap.c @@ -0,0 +1,65 @@ +/* + * test-treap.c: code to exercise the svn importer's treap structure + */ + +#include "cache.h" +#include "vcs-svn/obj_pool.h" +#include "vcs-svn/trp.h" + +struct int_node { + uintmax_t n; + struct trp_node children; +}; + +obj_pool_gen(node, struct int_node, 3) + +static int node_cmp(struct int_node *a, struct int_node *b) +{ + return (a->n > b->n) - (a->n < b->n); +} + +trp_gen(static, treap_, struct int_node, children, node, node_cmp) + +static void strtonode(struct int_node *item, const char *s) +{ + char *end; + item->n = strtoumax(s, &end, 10); + if (*s == '\0' || (*end != '\n' && *end != '\0')) + die("invalid integer: %s", s); +} + +int main(int argc, char *argv[]) +{ + struct strbuf sb = STRBUF_INIT; + struct trp_root root = { ~0 }; + uint32_t item; + + if (argc != 1) + usage("test-treap < ints"); + + while (strbuf_getline(&sb, stdin, '\n') != EOF) { + item = node_alloc(1); + strtonode(node_pointer(item), sb.buf); + treap_insert(&root, node_pointer(item)); + } + + item = node_offset(treap_first(&root)); + while (~item) { + uint32_t next; + struct int_node *tmp = node_pointer(node_alloc(1)); + + tmp->n = node_pointer(item)->n; + next = node_offset(treap_next(&root, node_pointer(item))); + + treap_remove(&root, node_pointer(item)); + item = node_offset(treap_nsearch(&root, tmp)); + + if (item != next && (!~item || node_pointer(item)->n != tmp->n)) + die("found %"PRIuMAX" in place of %"PRIuMAX"", + ~item ? node_pointer(item)->n : ~(uintmax_t) 0, + ~next ? node_pointer(next)->n : ~(uintmax_t) 0); + printf("%"PRIuMAX"\n", tmp->n); + } + node_reset(); + return 0; +} diff --git a/tree-walk.h b/tree-walk.h index 88ea7e9298..7e3e0b5ad1 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -28,7 +28,10 @@ static inline int tree_entry_len(const char *name, const unsigned char *sha1) void update_tree_entry(struct tree_desc *); void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size); -/* Helper function that does both of the above and returns true for success */ +/* + * Helper function that does both tree_entry_extract() and update_tree_entry() + * and returns true for success + */ int tree_entry(struct tree_desc *, struct name_entry *); void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); diff --git a/upload-pack.c b/upload-pack.c index fc79ddef25..92f9530c65 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -482,7 +482,7 @@ static int get_common_commits(void) static void receive_needs(void) { - struct object_array shallows = {0, 0, NULL}; + struct object_array shallows = OBJECT_ARRAY_INIT; static char line[1000]; int len, depth = 0; @@ -67,7 +67,8 @@ static int url_decode_char(const char *q) return val; } -static char *url_decode_internal(const char **query, const char *stop_at, struct strbuf *out) +static char *url_decode_internal(const char **query, const char *stop_at, + struct strbuf *out, int decode_plus) { const char *q = *query; @@ -90,7 +91,7 @@ static char *url_decode_internal(const char **query, const char *stop_at, struct } } - if (c == '+') + if (decode_plus && c == '+') strbuf_addch(out, ' '); else strbuf_addch(out, c); @@ -110,17 +111,17 @@ char *url_decode(const char *url) strbuf_add(&out, url, colon - url); url = colon; } - return url_decode_internal(&url, NULL, &out); + return url_decode_internal(&url, NULL, &out, 0); } char *url_decode_parameter_name(const char **query) { struct strbuf out = STRBUF_INIT; - return url_decode_internal(query, "&=", &out); + return url_decode_internal(query, "&=", &out, 1); } char *url_decode_parameter_value(const char **query) { struct strbuf out = STRBUF_INIT; - return url_decode_internal(query, "&", &out); + return url_decode_internal(query, "&", &out, 1); } diff --git a/vcs-svn/LICENSE b/vcs-svn/LICENSE new file mode 100644 index 0000000000..0a5e3c43a0 --- /dev/null +++ b/vcs-svn/LICENSE @@ -0,0 +1,33 @@ +Copyright (C) 2010 David Barr <david.barr@cordelta.com>. +All rights reserved. + +Copyright (C) 2008 Jason Evans <jasone@canonware.com>. +All rights reserved. + +Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH, +Frankfurt/Main, Germany +and others, see http://svn2cc.sarovar.org + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice(s), this list of conditions and the following disclaimer + unmodified other than the allowable addition of one or more + copyright notices. +2. Redistributions in binary form must reproduce the above copyright + notice(s), this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c new file mode 100644 index 0000000000..256a0522b2 --- /dev/null +++ b/vcs-svn/fast_export.c @@ -0,0 +1,75 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "fast_export.h" +#include "line_buffer.h" +#include "repo_tree.h" +#include "string_pool.h" + +#define MAX_GITSVN_LINE_LEN 4096 + +static uint32_t first_commit_done; + +void fast_export_delete(uint32_t depth, uint32_t *path) +{ + putchar('D'); + putchar(' '); + pool_print_seq(depth, path, '/', stdout); + putchar('\n'); +} + +void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, + uint32_t mark) +{ + /* Mode must be 100644, 100755, 120000, or 160000. */ + printf("M %06o :%d ", mode, mark); + pool_print_seq(depth, path, '/', stdout); + putchar('\n'); +} + +static char gitsvnline[MAX_GITSVN_LINE_LEN]; +void fast_export_commit(uint32_t revision, uint32_t author, char *log, + uint32_t uuid, uint32_t url, + unsigned long timestamp) +{ + if (!log) + log = ""; + if (~uuid && ~url) { + snprintf(gitsvnline, MAX_GITSVN_LINE_LEN, "\n\ngit-svn-id: %s@%d %s\n", + pool_fetch(url), revision, pool_fetch(uuid)); + } else { + *gitsvnline = '\0'; + } + printf("commit refs/heads/master\n"); + printf("committer %s <%s@%s> %ld +0000\n", + ~author ? pool_fetch(author) : "nobody", + ~author ? pool_fetch(author) : "nobody", + ~uuid ? pool_fetch(uuid) : "local", timestamp); + printf("data %"PRIu32"\n%s%s\n", + (uint32_t) (strlen(log) + strlen(gitsvnline)), + log, gitsvnline); + if (!first_commit_done) { + if (revision > 1) + printf("from refs/heads/master^0\n"); + first_commit_done = 1; + } + repo_diff(revision - 1, revision); + fputc('\n', stdout); + + printf("progress Imported commit %d.\n\n", revision); +} + +void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len) +{ + if (mode == REPO_MODE_LNK) { + /* svn symlink blobs start with "link " */ + buffer_skip_bytes(5); + len -= 5; + } + printf("blob\nmark :%d\ndata %d\n", mark, len); + buffer_copy_bytes(len); + fputc('\n', stdout); +} diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h new file mode 100644 index 0000000000..2aaaea53d5 --- /dev/null +++ b/vcs-svn/fast_export.h @@ -0,0 +1,11 @@ +#ifndef FAST_EXPORT_H_ +#define FAST_EXPORT_H_ + +void fast_export_delete(uint32_t depth, uint32_t *path); +void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, + uint32_t mark); +void fast_export_commit(uint32_t revision, uint32_t author, char *log, + uint32_t uuid, uint32_t url, unsigned long timestamp); +void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len); + +#endif diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c new file mode 100644 index 0000000000..1543567093 --- /dev/null +++ b/vcs-svn/line_buffer.c @@ -0,0 +1,97 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "line_buffer.h" +#include "obj_pool.h" + +#define LINE_BUFFER_LEN 10000 +#define COPY_BUFFER_LEN 4096 + +/* Create memory pool for char sequence of known length */ +obj_pool_gen(blob, char, 4096) + +static char line_buffer[LINE_BUFFER_LEN]; +static char byte_buffer[COPY_BUFFER_LEN]; +static FILE *infile; + +int buffer_init(const char *filename) +{ + infile = filename ? fopen(filename, "r") : stdin; + if (!infile) + return -1; + return 0; +} + +int buffer_deinit(void) +{ + int err; + if (infile == stdin) + return ferror(infile); + err = ferror(infile); + err |= fclose(infile); + return err; +} + +/* Read a line without trailing newline. */ +char *buffer_read_line(void) +{ + char *end; + if (!fgets(line_buffer, sizeof(line_buffer), infile)) + /* Error or data exhausted. */ + return NULL; + end = line_buffer + strlen(line_buffer); + if (end[-1] == '\n') + end[-1] = '\0'; + else if (feof(infile)) + ; /* No newline at end of file. That's fine. */ + else + /* + * Line was too long. + * There is probably a saner way to deal with this, + * but for now let's return an error. + */ + return NULL; + return line_buffer; +} + +char *buffer_read_string(uint32_t len) +{ + char *s; + blob_free(blob_pool.size); + s = blob_pointer(blob_alloc(len + 1)); + s[fread(s, 1, len, infile)] = '\0'; + return ferror(infile) ? NULL : s; +} + +void buffer_copy_bytes(uint32_t len) +{ + uint32_t in; + while (len > 0 && !feof(infile) && !ferror(infile)) { + in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + in = fread(byte_buffer, 1, in, infile); + len -= in; + fwrite(byte_buffer, 1, in, stdout); + if (ferror(stdout)) { + buffer_skip_bytes(len); + return; + } + } +} + +void buffer_skip_bytes(uint32_t len) +{ + uint32_t in; + while (len > 0 && !feof(infile) && !ferror(infile)) { + in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + in = fread(byte_buffer, 1, in, infile); + len -= in; + } +} + +void buffer_reset(void) +{ + blob_reset(); +} diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h new file mode 100644 index 0000000000..9c78ae11a1 --- /dev/null +++ b/vcs-svn/line_buffer.h @@ -0,0 +1,12 @@ +#ifndef LINE_BUFFER_H_ +#define LINE_BUFFER_H_ + +int buffer_init(const char *filename); +int buffer_deinit(void); +char *buffer_read_line(void); +char *buffer_read_string(uint32_t len); +void buffer_copy_bytes(uint32_t len); +void buffer_skip_bytes(uint32_t len); +void buffer_reset(void); + +#endif diff --git a/vcs-svn/line_buffer.txt b/vcs-svn/line_buffer.txt new file mode 100644 index 0000000000..8906fb1f50 --- /dev/null +++ b/vcs-svn/line_buffer.txt @@ -0,0 +1,58 @@ +line_buffer API +=============== + +The line_buffer library provides a convenient interface for +mostly-line-oriented input. + +Each line is not permitted to exceed 10000 bytes. The provided +functions are not thread-safe or async-signal-safe, and like +`fgets()`, they generally do not function correctly if interrupted +by a signal without SA_RESTART set. + +Calling sequence +---------------- + +The calling program: + + - specifies a file to read with `buffer_init` + - processes input with `buffer_read_line`, `buffer_read_string`, + `buffer_skip_bytes`, and `buffer_copy_bytes` + - closes the file with `buffer_deinit`, perhaps to start over and + read another file. + +Before exiting, the caller can use `buffer_reset` to deallocate +resources for the benefit of profiling tools. + +Functions +--------- + +`buffer_init`:: + Open the named file for input. If filename is NULL, + start reading from stdin. On failure, returns -1 (with + errno indicating the nature of the failure). + +`buffer_deinit`:: + Stop reading from the current file (closing it unless + it was stdin). Returns nonzero if `fclose` fails or + the error indicator was set. + +`buffer_read_line`:: + Read a line and strip off the trailing newline. + On failure or end of file, returns NULL. + +`buffer_read_string`:: + Read `len` characters of input or up to the end of the + file, whichever comes first. Returns NULL on error. + Returns whatever characters were read (possibly "") + for end of file. + +`buffer_copy_bytes`:: + Read `len` bytes of input and dump them to the standard output + stream. Returns early for error or end of file. + +`buffer_skip_bytes`:: + Discards `len` bytes from the input stream (stopping early + if necessary because of an error or eof). + +`buffer_reset`:: + Deallocates non-static buffers. diff --git a/vcs-svn/obj_pool.h b/vcs-svn/obj_pool.h new file mode 100644 index 0000000000..deb6eb8135 --- /dev/null +++ b/vcs-svn/obj_pool.h @@ -0,0 +1,61 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#ifndef OBJ_POOL_H_ +#define OBJ_POOL_H_ + +#include "git-compat-util.h" + +#define MAYBE_UNUSED __attribute__((__unused__)) + +#define obj_pool_gen(pre, obj_t, initial_capacity) \ +static struct { \ + uint32_t committed; \ + uint32_t size; \ + uint32_t capacity; \ + obj_t *base; \ +} pre##_pool = {0, 0, 0, NULL}; \ +static MAYBE_UNUSED uint32_t pre##_alloc(uint32_t count) \ +{ \ + uint32_t offset; \ + if (pre##_pool.size + count > pre##_pool.capacity) { \ + while (pre##_pool.size + count > pre##_pool.capacity) \ + if (pre##_pool.capacity) \ + pre##_pool.capacity *= 2; \ + else \ + pre##_pool.capacity = initial_capacity; \ + pre##_pool.base = realloc(pre##_pool.base, \ + pre##_pool.capacity * sizeof(obj_t)); \ + } \ + offset = pre##_pool.size; \ + pre##_pool.size += count; \ + return offset; \ +} \ +static MAYBE_UNUSED void pre##_free(uint32_t count) \ +{ \ + pre##_pool.size -= count; \ +} \ +static MAYBE_UNUSED uint32_t pre##_offset(obj_t *obj) \ +{ \ + return obj == NULL ? ~0 : obj - pre##_pool.base; \ +} \ +static MAYBE_UNUSED obj_t *pre##_pointer(uint32_t offset) \ +{ \ + return offset >= pre##_pool.size ? NULL : &pre##_pool.base[offset]; \ +} \ +static MAYBE_UNUSED void pre##_commit(void) \ +{ \ + pre##_pool.committed = pre##_pool.size; \ +} \ +static MAYBE_UNUSED void pre##_reset(void) \ +{ \ + free(pre##_pool.base); \ + pre##_pool.base = NULL; \ + pre##_pool.size = 0; \ + pre##_pool.capacity = 0; \ + pre##_pool.committed = 0; \ +} + +#endif diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c new file mode 100644 index 0000000000..e94d91d129 --- /dev/null +++ b/vcs-svn/repo_tree.c @@ -0,0 +1,329 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" + +#include "string_pool.h" +#include "repo_tree.h" +#include "obj_pool.h" +#include "fast_export.h" + +#include "trp.h" + +struct repo_dirent { + uint32_t name_offset; + struct trp_node children; + uint32_t mode; + uint32_t content_offset; +}; + +struct repo_dir { + struct trp_root entries; +}; + +struct repo_commit { + uint32_t root_dir_offset; +}; + +/* Memory pools for commit, dir and dirent */ +obj_pool_gen(commit, struct repo_commit, 4096) +obj_pool_gen(dir, struct repo_dir, 4096) +obj_pool_gen(dent, struct repo_dirent, 4096) + +static uint32_t active_commit; +static uint32_t mark; + +static int repo_dirent_name_cmp(const void *a, const void *b); + +/* Treap for directory entries */ +trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp); + +uint32_t next_blob_mark(void) +{ + return mark++; +} + +static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit) +{ + return dir_pointer(commit->root_dir_offset); +} + +static struct repo_dirent *repo_first_dirent(struct repo_dir *dir) +{ + return dent_first(&dir->entries); +} + +static int repo_dirent_name_cmp(const void *a, const void *b) +{ + const struct repo_dirent *dent1 = a, *dent2 = b; + uint32_t a_offset = dent1->name_offset; + uint32_t b_offset = dent2->name_offset; + return (a_offset > b_offset) - (a_offset < b_offset); +} + +static int repo_dirent_is_dir(struct repo_dirent *dent) +{ + return dent != NULL && dent->mode == REPO_MODE_DIR; +} + +static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent) +{ + if (!repo_dirent_is_dir(dent)) + return NULL; + return dir_pointer(dent->content_offset); +} + +static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir) +{ + uint32_t orig_o, new_o; + orig_o = dir_offset(orig_dir); + if (orig_o >= dir_pool.committed) + return orig_dir; + new_o = dir_alloc(1); + orig_dir = dir_pointer(orig_o); + *dir_pointer(new_o) = *orig_dir; + return dir_pointer(new_o); +} + +static struct repo_dirent *repo_read_dirent(uint32_t revision, uint32_t *path) +{ + uint32_t name = 0; + struct repo_dirent *key = dent_pointer(dent_alloc(1)); + struct repo_dir *dir = NULL; + struct repo_dirent *dent = NULL; + dir = repo_commit_root_dir(commit_pointer(revision)); + while (~(name = *path++)) { + key->name_offset = name; + dent = dent_search(&dir->entries, key); + if (dent == NULL || !repo_dirent_is_dir(dent)) + break; + dir = repo_dir_from_dirent(dent); + } + dent_free(1); + return dent; +} + +static void repo_write_dirent(uint32_t *path, uint32_t mode, + uint32_t content_offset, uint32_t del) +{ + uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0; + struct repo_dir *dir; + struct repo_dirent *key; + struct repo_dirent *dent = NULL; + revision = active_commit; + dir = repo_commit_root_dir(commit_pointer(revision)); + dir = repo_clone_dir(dir); + commit_pointer(revision)->root_dir_offset = dir_offset(dir); + while (~(name = *path++)) { + parent_dir_o = dir_offset(dir); + + key = dent_pointer(dent_alloc(1)); + key->name_offset = name; + + dent = dent_search(&dir->entries, key); + if (dent == NULL) + dent = key; + else + dent_free(1); + + if (dent == key) { + dent->mode = REPO_MODE_DIR; + dent->content_offset = 0; + dent_insert(&dir->entries, dent); + } + + if (dent_offset(dent) < dent_pool.committed) { + dir_o = repo_dirent_is_dir(dent) ? + dent->content_offset : ~0; + dent_remove(&dir->entries, dent); + dent = dent_pointer(dent_alloc(1)); + dent->name_offset = name; + dent->mode = REPO_MODE_DIR; + dent->content_offset = dir_o; + dent_insert(&dir->entries, dent); + } + + dir = repo_dir_from_dirent(dent); + dir = repo_clone_dir(dir); + dent->content_offset = dir_offset(dir); + } + if (dent == NULL) + return; + dent->mode = mode; + dent->content_offset = content_offset; + if (del && ~parent_dir_o) + dent_remove(&dir_pointer(parent_dir_o)->entries, dent); +} + +uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst) +{ + uint32_t mode = 0, content_offset = 0; + struct repo_dirent *src_dent; + src_dent = repo_read_dirent(revision, src); + if (src_dent != NULL) { + mode = src_dent->mode; + content_offset = src_dent->content_offset; + repo_write_dirent(dst, mode, content_offset, 0); + } + return mode; +} + +void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark) +{ + repo_write_dirent(path, mode, blob_mark, 0); +} + +uint32_t repo_replace(uint32_t *path, uint32_t blob_mark) +{ + uint32_t mode = 0; + struct repo_dirent *src_dent; + src_dent = repo_read_dirent(active_commit, path); + if (src_dent != NULL) { + mode = src_dent->mode; + repo_write_dirent(path, mode, blob_mark, 0); + } + return mode; +} + +void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark) +{ + struct repo_dirent *src_dent; + src_dent = repo_read_dirent(active_commit, path); + if (src_dent != NULL && blob_mark == 0) + blob_mark = src_dent->content_offset; + repo_write_dirent(path, mode, blob_mark, 0); +} + +void repo_delete(uint32_t *path) +{ + repo_write_dirent(path, 0, 0, 1); +} + +static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir); + +static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent) +{ + if (repo_dirent_is_dir(dent)) + repo_git_add_r(depth, path, repo_dir_from_dirent(dent)); + else + fast_export_modify(depth, path, + dent->mode, dent->content_offset); +} + +static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir) +{ + struct repo_dirent *de = repo_first_dirent(dir); + while (de) { + path[depth] = de->name_offset; + repo_git_add(depth + 1, path, de); + de = dent_next(&dir->entries, de); + } +} + +static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1, + struct repo_dir *dir2) +{ + struct repo_dirent *de1, *de2; + de1 = repo_first_dirent(dir1); + de2 = repo_first_dirent(dir2); + + while (de1 && de2) { + if (de1->name_offset < de2->name_offset) { + path[depth] = de1->name_offset; + fast_export_delete(depth + 1, path); + de1 = dent_next(&dir1->entries, de1); + continue; + } + if (de1->name_offset > de2->name_offset) { + path[depth] = de2->name_offset; + repo_git_add(depth + 1, path, de2); + de2 = dent_next(&dir2->entries, de2); + continue; + } + path[depth] = de1->name_offset; + + if (de1->mode == de2->mode && + de1->content_offset == de2->content_offset) { + ; /* No change. */ + } else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) { + repo_diff_r(depth + 1, path, + repo_dir_from_dirent(de1), + repo_dir_from_dirent(de2)); + } else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) { + repo_git_add(depth + 1, path, de2); + } else { + fast_export_delete(depth + 1, path); + repo_git_add(depth + 1, path, de2); + } + de1 = dent_next(&dir1->entries, de1); + de2 = dent_next(&dir2->entries, de2); + } + while (de1) { + path[depth] = de1->name_offset; + fast_export_delete(depth + 1, path); + de1 = dent_next(&dir1->entries, de1); + } + while (de2) { + path[depth] = de2->name_offset; + repo_git_add(depth + 1, path, de2); + de2 = dent_next(&dir2->entries, de2); + } +} + +static uint32_t path_stack[REPO_MAX_PATH_DEPTH]; + +void repo_diff(uint32_t r1, uint32_t r2) +{ + repo_diff_r(0, + path_stack, + repo_commit_root_dir(commit_pointer(r1)), + repo_commit_root_dir(commit_pointer(r2))); +} + +void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid, + uint32_t url, unsigned long timestamp) +{ + fast_export_commit(revision, author, log, uuid, url, timestamp); + dent_commit(); + dir_commit(); + active_commit = commit_alloc(1); + commit_pointer(active_commit)->root_dir_offset = + commit_pointer(active_commit - 1)->root_dir_offset; +} + +static void mark_init(void) +{ + uint32_t i; + mark = 0; + for (i = 0; i < dent_pool.size; i++) + if (!repo_dirent_is_dir(dent_pointer(i)) && + dent_pointer(i)->content_offset > mark) + mark = dent_pointer(i)->content_offset; + mark++; +} + +void repo_init(void) +{ + mark_init(); + if (commit_pool.size == 0) { + /* Create empty tree for commit 0. */ + commit_alloc(1); + commit_pointer(0)->root_dir_offset = dir_alloc(1); + dir_pointer(0)->entries.trp_root = ~0; + dir_commit(); + } + /* Preallocate next commit, ready for changes. */ + active_commit = commit_alloc(1); + commit_pointer(active_commit)->root_dir_offset = + commit_pointer(active_commit - 1)->root_dir_offset; +} + +void repo_reset(void) +{ + pool_reset(); + commit_reset(); + dir_reset(); + dent_reset(); +} diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h new file mode 100644 index 0000000000..5476175922 --- /dev/null +++ b/vcs-svn/repo_tree.h @@ -0,0 +1,26 @@ +#ifndef REPO_TREE_H_ +#define REPO_TREE_H_ + +#include "git-compat-util.h" + +#define REPO_MODE_DIR 0040000 +#define REPO_MODE_BLB 0100644 +#define REPO_MODE_EXE 0100755 +#define REPO_MODE_LNK 0120000 + +#define REPO_MAX_PATH_LEN 4096 +#define REPO_MAX_PATH_DEPTH 1000 + +uint32_t next_blob_mark(void); +uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst); +void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark); +uint32_t repo_replace(uint32_t *path, uint32_t blob_mark); +void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark); +void repo_delete(uint32_t *path); +void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid, + uint32_t url, long unsigned timestamp); +void repo_diff(uint32_t r1, uint32_t r2); +void repo_init(void); +void repo_reset(void); + +#endif diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c new file mode 100644 index 0000000000..f5b1da836e --- /dev/null +++ b/vcs-svn/string_pool.c @@ -0,0 +1,102 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "trp.h" +#include "obj_pool.h" +#include "string_pool.h" + +static struct trp_root tree = { ~0 }; + +struct node { + uint32_t offset; + struct trp_node children; +}; + +/* Two memory pools: one for struct node, and another for strings */ +obj_pool_gen(node, struct node, 4096) +obj_pool_gen(string, char, 4096) + +static char *node_value(struct node *node) +{ + return node ? string_pointer(node->offset) : NULL; +} + +static int node_cmp(struct node *a, struct node *b) +{ + return strcmp(node_value(a), node_value(b)); +} + +/* Build a Treap from the node structure (a trp_node w/ offset) */ +trp_gen(static, tree_, struct node, children, node, node_cmp); + +const char *pool_fetch(uint32_t entry) +{ + return node_value(node_pointer(entry)); +} + +uint32_t pool_intern(const char *key) +{ + /* Canonicalize key */ + struct node *match = NULL, *node; + uint32_t key_len; + if (key == NULL) + return ~0; + key_len = strlen(key) + 1; + node = node_pointer(node_alloc(1)); + node->offset = string_alloc(key_len); + strcpy(node_value(node), key); + match = tree_search(&tree, node); + if (!match) { + tree_insert(&tree, node); + } else { + node_free(1); + string_free(key_len); + node = match; + } + return node_offset(node); +} + +uint32_t pool_tok_r(char *str, const char *delim, char **saveptr) +{ + char *token = strtok_r(str, delim, saveptr); + return token ? pool_intern(token) : ~0; +} + +void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream) +{ + uint32_t i; + for (i = 0; i < len && ~seq[i]; i++) { + fputs(pool_fetch(seq[i]), stream); + if (i < len - 1 && ~seq[i + 1]) + fputc(delim, stream); + } +} + +uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str) +{ + char *context = NULL; + uint32_t token = ~0; + uint32_t length; + + if (sz == 0) + return ~0; + if (str) + token = pool_tok_r(str, delim, &context); + for (length = 0; length < sz; length++) { + seq[length] = token; + if (token == ~0) + return length; + token = pool_tok_r(NULL, delim, &context); + } + seq[sz - 1] = ~0; + return sz; +} + +void pool_reset(void) +{ + node_reset(); + string_reset(); +} diff --git a/vcs-svn/string_pool.h b/vcs-svn/string_pool.h new file mode 100644 index 0000000000..222fb66e68 --- /dev/null +++ b/vcs-svn/string_pool.h @@ -0,0 +1,11 @@ +#ifndef STRING_POOL_H_ +#define STRING_POOL_H_ + +uint32_t pool_intern(const char *key); +const char *pool_fetch(uint32_t entry); +uint32_t pool_tok_r(char *str, const char *delim, char **saveptr); +void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream); +uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str); +void pool_reset(void); + +#endif diff --git a/vcs-svn/string_pool.txt b/vcs-svn/string_pool.txt new file mode 100644 index 0000000000..1b41f15628 --- /dev/null +++ b/vcs-svn/string_pool.txt @@ -0,0 +1,43 @@ +string_pool API +=============== + +The string_pool API provides facilities for replacing strings +with integer keys that can be more easily compared and stored. +The facilities are designed so that one could teach Git without +too much trouble to store the information needed for these keys to +remain valid over multiple executions. + +Functions +--------- + +pool_intern:: + Include a string in the string pool and get its key. + If that string is already in the pool, retrieves its + existing key. + +pool_fetch:: + Retrieve the string associated to a given key. + +pool_tok_r:: + Extract the key of the next token from a string. + Interface mimics strtok_r. + +pool_print_seq:: + Print a sequence of strings named by key to a file, using the + specified delimiter to separate them. + + If NULL (key ~0) appears in the sequence, the sequence ends + early. + +pool_tok_seq:: + Split a string into tokens, storing the keys of segments + into a caller-provided array. + + Unless sz is 0, the array will always be ~0-terminated. + If there is not enough room for all the tokens, the + array holds as many tokens as fit in the entries before + the terminating ~0. Return value is the index after the + last token, or sz if the tokens did not fit. + +pool_reset:: + Deallocate storage for the string pool. diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c new file mode 100644 index 0000000000..630eeb53b7 --- /dev/null +++ b/vcs-svn/svndump.c @@ -0,0 +1,302 @@ +/* + * Parse and rearrange a svnadmin dump. + * Create the dump with: + * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile + * + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "cache.h" +#include "repo_tree.h" +#include "fast_export.h" +#include "line_buffer.h" +#include "obj_pool.h" +#include "string_pool.h" + +#define NODEACT_REPLACE 4 +#define NODEACT_DELETE 3 +#define NODEACT_ADD 2 +#define NODEACT_CHANGE 1 +#define NODEACT_UNKNOWN 0 + +#define DUMP_CTX 0 +#define REV_CTX 1 +#define NODE_CTX 2 + +#define LENGTH_UNKNOWN (~0) +#define DATE_RFC2822_LEN 31 + +/* Create memory pool for log messages */ +obj_pool_gen(log, char, 4096) + +static char* log_copy(uint32_t length, char *log) +{ + char *buffer; + log_free(log_pool.size); + buffer = log_pointer(log_alloc(length)); + strncpy(buffer, log, length); + return buffer; +} + +static struct { + uint32_t action, propLength, textLength, srcRev, srcMode, mark, type; + uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH]; +} node_ctx; + +static struct { + uint32_t revision, author; + unsigned long timestamp; + char *log; +} rev_ctx; + +static struct { + uint32_t uuid, url; +} dump_ctx; + +static struct { + uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid, + revision_number, node_path, node_kind, node_action, + node_copyfrom_path, node_copyfrom_rev, text_content_length, + prop_content_length, content_length; +} keys; + +static void reset_node_ctx(char *fname) +{ + node_ctx.type = 0; + node_ctx.action = NODEACT_UNKNOWN; + node_ctx.propLength = LENGTH_UNKNOWN; + node_ctx.textLength = LENGTH_UNKNOWN; + node_ctx.src[0] = ~0; + node_ctx.srcRev = 0; + node_ctx.srcMode = 0; + pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname); + node_ctx.mark = 0; +} + +static void reset_rev_ctx(uint32_t revision) +{ + rev_ctx.revision = revision; + rev_ctx.timestamp = 0; + rev_ctx.log = NULL; + rev_ctx.author = ~0; +} + +static void reset_dump_ctx(uint32_t url) +{ + dump_ctx.url = url; + dump_ctx.uuid = ~0; +} + +static void init_keys(void) +{ + keys.svn_log = pool_intern("svn:log"); + keys.svn_author = pool_intern("svn:author"); + keys.svn_date = pool_intern("svn:date"); + keys.svn_executable = pool_intern("svn:executable"); + keys.svn_special = pool_intern("svn:special"); + keys.uuid = pool_intern("UUID"); + keys.revision_number = pool_intern("Revision-number"); + keys.node_path = pool_intern("Node-path"); + keys.node_kind = pool_intern("Node-kind"); + keys.node_action = pool_intern("Node-action"); + keys.node_copyfrom_path = pool_intern("Node-copyfrom-path"); + keys.node_copyfrom_rev = pool_intern("Node-copyfrom-rev"); + keys.text_content_length = pool_intern("Text-content-length"); + keys.prop_content_length = pool_intern("Prop-content-length"); + keys.content_length = pool_intern("Content-length"); +} + +static void read_props(void) +{ + uint32_t len; + uint32_t key = ~0; + char *val = NULL; + char *t; + while ((t = buffer_read_line()) && strcmp(t, "PROPS-END")) { + if (!strncmp(t, "K ", 2)) { + len = atoi(&t[2]); + key = pool_intern(buffer_read_string(len)); + buffer_read_line(); + } else if (!strncmp(t, "V ", 2)) { + len = atoi(&t[2]); + val = buffer_read_string(len); + if (key == keys.svn_log) { + /* Value length excludes terminating nul. */ + rev_ctx.log = log_copy(len + 1, val); + } else if (key == keys.svn_author) { + rev_ctx.author = pool_intern(val); + } else if (key == keys.svn_date) { + if (parse_date_basic(val, &rev_ctx.timestamp, NULL)) + fprintf(stderr, "Invalid timestamp: %s\n", val); + } else if (key == keys.svn_executable) { + node_ctx.type = REPO_MODE_EXE; + } else if (key == keys.svn_special) { + node_ctx.type = REPO_MODE_LNK; + } + key = ~0; + buffer_read_line(); + } + } +} + +static void handle_node(void) +{ + if (node_ctx.propLength != LENGTH_UNKNOWN && node_ctx.propLength) + read_props(); + + if (node_ctx.srcRev) + node_ctx.srcMode = repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst); + + if (node_ctx.textLength != LENGTH_UNKNOWN && + node_ctx.type != REPO_MODE_DIR) + node_ctx.mark = next_blob_mark(); + + if (node_ctx.action == NODEACT_DELETE) { + repo_delete(node_ctx.dst); + } else if (node_ctx.action == NODEACT_CHANGE || + node_ctx.action == NODEACT_REPLACE) { + if (node_ctx.action == NODEACT_REPLACE && + node_ctx.type == REPO_MODE_DIR) + repo_replace(node_ctx.dst, node_ctx.mark); + else if (node_ctx.propLength != LENGTH_UNKNOWN) + repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark); + else if (node_ctx.textLength != LENGTH_UNKNOWN) + node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark); + } else if (node_ctx.action == NODEACT_ADD) { + if (node_ctx.srcRev && node_ctx.propLength != LENGTH_UNKNOWN) + repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark); + else if (node_ctx.srcRev && node_ctx.textLength != LENGTH_UNKNOWN) + node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark); + else if ((node_ctx.type == REPO_MODE_DIR && !node_ctx.srcRev) || + node_ctx.textLength != LENGTH_UNKNOWN) + repo_add(node_ctx.dst, node_ctx.type, node_ctx.mark); + } + + if (node_ctx.propLength == LENGTH_UNKNOWN && node_ctx.srcMode) + node_ctx.type = node_ctx.srcMode; + + if (node_ctx.mark) + fast_export_blob(node_ctx.type, node_ctx.mark, node_ctx.textLength); + else if (node_ctx.textLength != LENGTH_UNKNOWN) + buffer_skip_bytes(node_ctx.textLength); +} + +static void handle_revision(void) +{ + if (rev_ctx.revision) + repo_commit(rev_ctx.revision, rev_ctx.author, rev_ctx.log, + dump_ctx.uuid, dump_ctx.url, rev_ctx.timestamp); +} + +void svndump_read(const char *url) +{ + char *val; + char *t; + uint32_t active_ctx = DUMP_CTX; + uint32_t len; + uint32_t key; + + reset_dump_ctx(pool_intern(url)); + while ((t = buffer_read_line())) { + val = strstr(t, ": "); + if (!val) + continue; + *val++ = '\0'; + *val++ = '\0'; + key = pool_intern(t); + + if (key == keys.uuid) { + dump_ctx.uuid = pool_intern(val); + } else if (key == keys.revision_number) { + if (active_ctx == NODE_CTX) + handle_node(); + if (active_ctx != DUMP_CTX) + handle_revision(); + active_ctx = REV_CTX; + reset_rev_ctx(atoi(val)); + } else if (key == keys.node_path) { + if (active_ctx == NODE_CTX) + handle_node(); + active_ctx = NODE_CTX; + reset_node_ctx(val); + } else if (key == keys.node_kind) { + if (!strcmp(val, "dir")) + node_ctx.type = REPO_MODE_DIR; + else if (!strcmp(val, "file")) + node_ctx.type = REPO_MODE_BLB; + else + fprintf(stderr, "Unknown node-kind: %s\n", val); + } else if (key == keys.node_action) { + if (!strcmp(val, "delete")) { + node_ctx.action = NODEACT_DELETE; + } else if (!strcmp(val, "add")) { + node_ctx.action = NODEACT_ADD; + } else if (!strcmp(val, "change")) { + node_ctx.action = NODEACT_CHANGE; + } else if (!strcmp(val, "replace")) { + node_ctx.action = NODEACT_REPLACE; + } else { + fprintf(stderr, "Unknown node-action: %s\n", val); + node_ctx.action = NODEACT_UNKNOWN; + } + } else if (key == keys.node_copyfrom_path) { + pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val); + } else if (key == keys.node_copyfrom_rev) { + node_ctx.srcRev = atoi(val); + } else if (key == keys.text_content_length) { + node_ctx.textLength = atoi(val); + } else if (key == keys.prop_content_length) { + node_ctx.propLength = atoi(val); + } else if (key == keys.content_length) { + len = atoi(val); + buffer_read_line(); + if (active_ctx == REV_CTX) { + read_props(); + } else if (active_ctx == NODE_CTX) { + handle_node(); + active_ctx = REV_CTX; + } else { + fprintf(stderr, "Unexpected content length header: %d\n", len); + buffer_skip_bytes(len); + } + } + } + if (active_ctx == NODE_CTX) + handle_node(); + if (active_ctx != DUMP_CTX) + handle_revision(); +} + +void svndump_init(const char *filename) +{ + buffer_init(filename); + repo_init(); + reset_dump_ctx(~0); + reset_rev_ctx(0); + reset_node_ctx(NULL); + init_keys(); +} + +void svndump_deinit(void) +{ + log_reset(); + repo_reset(); + reset_dump_ctx(~0); + reset_rev_ctx(0); + reset_node_ctx(NULL); + if (buffer_deinit()) + fprintf(stderr, "Input error\n"); + if (ferror(stdout)) + fprintf(stderr, "Output error\n"); +} + +void svndump_reset(void) +{ + log_reset(); + buffer_reset(); + repo_reset(); + reset_dump_ctx(~0); + reset_rev_ctx(0); + reset_node_ctx(NULL); +} diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h new file mode 100644 index 0000000000..93c412f14a --- /dev/null +++ b/vcs-svn/svndump.h @@ -0,0 +1,9 @@ +#ifndef SVNDUMP_H_ +#define SVNDUMP_H_ + +void svndump_init(const char *filename); +void svndump_read(const char *url); +void svndump_deinit(void); +void svndump_reset(void); + +#endif diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h new file mode 100644 index 0000000000..ee35c688a0 --- /dev/null +++ b/vcs-svn/trp.h @@ -0,0 +1,236 @@ +/* + * C macro implementation of treaps. + * + * Usage: + * #include <stdint.h> + * #include "trp.h" + * trp_gen(...) + * + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#ifndef TRP_H_ +#define TRP_H_ + +#define MAYBE_UNUSED __attribute__((__unused__)) + +/* Node structure. */ +struct trp_node { + uint32_t trpn_left; + uint32_t trpn_right; +}; + +/* Root structure. */ +struct trp_root { + uint32_t trp_root; +}; + +/* Pointer/Offset conversion. */ +#define trpn_pointer(a_base, a_offset) (a_base##_pointer(a_offset)) +#define trpn_offset(a_base, a_pointer) (a_base##_offset(a_pointer)) +#define trpn_modify(a_base, a_offset) \ + do { \ + if ((a_offset) < a_base##_pool.committed) { \ + uint32_t old_offset = (a_offset);\ + (a_offset) = a_base##_alloc(1); \ + *trpn_pointer(a_base, a_offset) = \ + *trpn_pointer(a_base, old_offset); \ + } \ + } while (0) + +/* Left accessors. */ +#define trp_left_get(a_base, a_field, a_node) \ + (trpn_pointer(a_base, a_node)->a_field.trpn_left) +#define trp_left_set(a_base, a_field, a_node, a_left) \ + do { \ + trpn_modify(a_base, a_node); \ + trp_left_get(a_base, a_field, a_node) = (a_left); \ + } while (0) + +/* Right accessors. */ +#define trp_right_get(a_base, a_field, a_node) \ + (trpn_pointer(a_base, a_node)->a_field.trpn_right) +#define trp_right_set(a_base, a_field, a_node, a_right) \ + do { \ + trpn_modify(a_base, a_node); \ + trp_right_get(a_base, a_field, a_node) = (a_right); \ + } while (0) + +/* + * Fibonacci hash function. + * The multiplier is the nearest prime to (2^32 times (√5 - 1)/2). + * See Knuth §6.4: volume 3, 3rd ed, p518. + */ +#define trpn_hash(a_node) (uint32_t) (2654435761u * (a_node)) + +/* Priority accessors. */ +#define trp_prio_get(a_node) trpn_hash(a_node) + +/* Node initializer. */ +#define trp_node_new(a_base, a_field, a_node) \ + do { \ + trp_left_set(a_base, a_field, (a_node), ~0); \ + trp_right_set(a_base, a_field, (a_node), ~0); \ + } while (0) + +/* Internal utility macros. */ +#define trpn_first(a_base, a_field, a_root, r_node) \ + do { \ + (r_node) = (a_root); \ + if ((r_node) == ~0) \ + return NULL; \ + while (~trp_left_get(a_base, a_field, (r_node))) \ + (r_node) = trp_left_get(a_base, a_field, (r_node)); \ + } while (0) + +#define trpn_rotate_left(a_base, a_field, a_node, r_node) \ + do { \ + (r_node) = trp_right_get(a_base, a_field, (a_node)); \ + trp_right_set(a_base, a_field, (a_node), \ + trp_left_get(a_base, a_field, (r_node))); \ + trp_left_set(a_base, a_field, (r_node), (a_node)); \ + } while (0) + +#define trpn_rotate_right(a_base, a_field, a_node, r_node) \ + do { \ + (r_node) = trp_left_get(a_base, a_field, (a_node)); \ + trp_left_set(a_base, a_field, (a_node), \ + trp_right_get(a_base, a_field, (r_node))); \ + trp_right_set(a_base, a_field, (r_node), (a_node)); \ + } while (0) + +#define trp_gen(a_attr, a_pre, a_type, a_field, a_base, a_cmp) \ +a_attr a_type MAYBE_UNUSED *a_pre##first(struct trp_root *treap) \ +{ \ + uint32_t ret; \ + trpn_first(a_base, a_field, treap->trp_root, ret); \ + return trpn_pointer(a_base, ret); \ +} \ +a_attr a_type MAYBE_UNUSED *a_pre##next(struct trp_root *treap, a_type *node) \ +{ \ + uint32_t ret; \ + uint32_t offset = trpn_offset(a_base, node); \ + if (~trp_right_get(a_base, a_field, offset)) { \ + trpn_first(a_base, a_field, \ + trp_right_get(a_base, a_field, offset), ret); \ + } else { \ + uint32_t tnode = treap->trp_root; \ + ret = ~0; \ + while (1) { \ + int cmp = (a_cmp)(trpn_pointer(a_base, offset), \ + trpn_pointer(a_base, tnode)); \ + if (cmp < 0) { \ + ret = tnode; \ + tnode = trp_left_get(a_base, a_field, tnode); \ + } else if (cmp > 0) { \ + tnode = trp_right_get(a_base, a_field, tnode); \ + } else { \ + break; \ + } \ + } \ + } \ + return trpn_pointer(a_base, ret); \ +} \ +a_attr a_type MAYBE_UNUSED *a_pre##search(struct trp_root *treap, a_type *key) \ +{ \ + int cmp; \ + uint32_t ret = treap->trp_root; \ + while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \ + if (cmp < 0) { \ + ret = trp_left_get(a_base, a_field, ret); \ + } else { \ + ret = trp_right_get(a_base, a_field, ret); \ + } \ + } \ + return trpn_pointer(a_base, ret); \ +} \ +a_attr a_type MAYBE_UNUSED *a_pre##nsearch(struct trp_root *treap, a_type *key) \ +{ \ + int cmp; \ + uint32_t ret = treap->trp_root; \ + while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \ + if (cmp < 0) { \ + if (!~trp_left_get(a_base, a_field, ret)) \ + break; \ + ret = trp_left_get(a_base, a_field, ret); \ + } else { \ + ret = trp_right_get(a_base, a_field, ret); \ + } \ + } \ + return trpn_pointer(a_base, ret); \ +} \ +a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t ins_node) \ +{ \ + if (cur_node == ~0) { \ + return ins_node; \ + } else { \ + uint32_t ret; \ + int cmp = (a_cmp)(trpn_pointer(a_base, ins_node), \ + trpn_pointer(a_base, cur_node)); \ + if (cmp < 0) { \ + uint32_t left = a_pre##insert_recurse( \ + trp_left_get(a_base, a_field, cur_node), ins_node); \ + trp_left_set(a_base, a_field, cur_node, left); \ + if (trp_prio_get(left) < trp_prio_get(cur_node)) \ + trpn_rotate_right(a_base, a_field, cur_node, ret); \ + else \ + ret = cur_node; \ + } else { \ + uint32_t right = a_pre##insert_recurse( \ + trp_right_get(a_base, a_field, cur_node), ins_node); \ + trp_right_set(a_base, a_field, cur_node, right); \ + if (trp_prio_get(right) < trp_prio_get(cur_node)) \ + trpn_rotate_left(a_base, a_field, cur_node, ret); \ + else \ + ret = cur_node; \ + } \ + return ret; \ + } \ +} \ +a_attr void MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \ +{ \ + uint32_t offset = trpn_offset(a_base, node); \ + trp_node_new(a_base, a_field, offset); \ + treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \ +} \ +a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \ +{ \ + int cmp = a_cmp(trpn_pointer(a_base, rem_node), \ + trpn_pointer(a_base, cur_node)); \ + if (cmp == 0) { \ + uint32_t ret; \ + uint32_t left = trp_left_get(a_base, a_field, cur_node); \ + uint32_t right = trp_right_get(a_base, a_field, cur_node); \ + if (left == ~0) { \ + if (right == ~0) \ + return ~0; \ + } else if (right == ~0 || trp_prio_get(left) < trp_prio_get(right)) { \ + trpn_rotate_right(a_base, a_field, cur_node, ret); \ + right = a_pre##remove_recurse(cur_node, rem_node); \ + trp_right_set(a_base, a_field, ret, right); \ + return ret; \ + } \ + trpn_rotate_left(a_base, a_field, cur_node, ret); \ + left = a_pre##remove_recurse(cur_node, rem_node); \ + trp_left_set(a_base, a_field, ret, left); \ + return ret; \ + } else if (cmp < 0) { \ + uint32_t left = a_pre##remove_recurse( \ + trp_left_get(a_base, a_field, cur_node), rem_node); \ + trp_left_set(a_base, a_field, cur_node, left); \ + return cur_node; \ + } else { \ + uint32_t right = a_pre##remove_recurse( \ + trp_right_get(a_base, a_field, cur_node), rem_node); \ + trp_right_set(a_base, a_field, cur_node, right); \ + return cur_node; \ + } \ +} \ +a_attr void MAYBE_UNUSED a_pre##remove(struct trp_root *treap, a_type *node) \ +{ \ + treap->trp_root = a_pre##remove_recurse(treap->trp_root, \ + trpn_offset(a_base, node)); \ +} \ + +#endif diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt new file mode 100644 index 0000000000..eb4c191875 --- /dev/null +++ b/vcs-svn/trp.txt @@ -0,0 +1,103 @@ +Motivation +========== + +Treaps provide a memory-efficient binary search tree structure. +Insertion/deletion/search are about as about as fast in the average +case as red-black trees and the chances of worst-case behavior are +vanishingly small, thanks to (pseudo-)randomness. The bad worst-case +behavior is a small price to pay, given that treaps are much simpler +to implement. + +API +=== + +The trp API generates a data structure and functions to handle a +large growing set of objects stored in a pool. + +The caller: + +. Specifies parameters for the generated functions with the + trp_gen(static, foo_, ...) macro. + +. Allocates a `struct trp_root` variable and sets it to {~0}. + +. Adds new nodes to the set using `foo_insert`. + +. Can find a specific item in the set using `foo_search`. + +. Can iterate over items in the set using `foo_first` and `foo_next`. + +. Can remove an item from the set using `foo_remove`. + +Example: + +---- +struct ex_node { + const char *s; + struct trp_node ex_link; +}; +static struct trp_root ex_base = {~0}; +obj_pool_gen(ex, struct ex_node, 4096); +trp_gen(static, ex_, struct ex_node, ex_link, ex, strcmp) +struct ex_node *item; + +item = ex_pointer(ex_alloc(1)); +item->s = "hello"; +ex_insert(&ex_base, item); +item = ex_pointer(ex_alloc(1)); +item->s = "goodbye"; +ex_insert(&ex_base, item); +for (item = ex_first(&ex_base); item; item = ex_next(&ex_base, item)) + printf("%s\n", item->s); +---- + +Functions +--------- + +trp_gen(attr, foo_, node_type, link_field, pool, cmp):: + + Generate a type-specific treap implementation. ++ +. The storage class for generated functions will be 'attr' (e.g., `static`). +. Generated function names are prefixed with 'foo_' (e.g., `treap_`). +. Treap nodes will be of type 'node_type' (e.g., `struct treap_node`). + This type must be a struct with at least one `struct trp_node` field + to point to its children. +. The field used to access child nodes will be 'link_field'. +. All treap nodes must lie in the 'pool' object pool. +. Treap nodes must be totally ordered by the 'cmp' relation, with the + following prototype: ++ +int (*cmp)(node_type \*a, node_type \*b) ++ +and returning a value less than, equal to, or greater than zero +according to the result of comparison. + +void foo_insert(struct trp_root *treap, node_type \*node):: + + Insert node into treap. If inserted multiple times, + a node will appear in the treap multiple times. + +void foo_remove(struct trp_root *treap, node_type \*node):: + + Remove node from treap. Caller must ensure node is + present in treap before using this function. + +node_type *foo_search(struct trp_root \*treap, node_type \*key):: + + Search for a node that matches key. If no match is found, + result is NULL. + +node_type *foo_nsearch(struct trp_root \*treap, node_type \*key):: + + Like `foo_search`, but if if the key is missing return what + would be key's successor, were key in treap (NULL if no + successor). + +node_type *foo_first(struct trp_root \*treap):: + + Find the first item from the treap, in sorted order. + +node_type *foo_next(struct trp_root \*treap, node_type \*node):: + + Find the next item. |