diff options
117 files changed, 2389 insertions, 466 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile index 5cd8b63ac5..144ec32f12 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -32,6 +32,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) prefix?=$(HOME) bindir?=$(prefix)/bin htmldir?=$(prefix)/share/doc/git-doc +pdfdir?=$(prefix)/share/doc/git-doc mandir?=$(prefix)/share/man man1dir=$(mandir)/man1 man5dir=$(mandir)/man5 @@ -50,6 +51,7 @@ infodir?=$(prefix)/share/info MAKEINFO=makeinfo INSTALL_INFO=install-info DOCBOOK2X_TEXI=docbook2x-texi +DBLATEX=dblatex ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif @@ -87,6 +89,8 @@ man7: $(DOC_MAN7) info: git.info gitman.info +pdf: user-manual.pdf + install: install-man install-man: man @@ -107,6 +111,10 @@ install-info: info echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \ fi +install-pdf: pdf + $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir) + $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) + install-html: html sh ./install-webdoc.sh $(DESTDIR)$(htmldir) @@ -191,6 +199,11 @@ user-manual.texi: user-manual.xml $(PERL_PATH) fix-texi.perl >$@+ mv $@+ $@ +user-manual.pdf: user-manual.xml + $(RM) $@+ $@ + $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< + mv $@+ $@ + gitman.texi: $(MAN_XML) cat-texi.perl $(RM) $@+ $@ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ diff --git a/Documentation/RelNotes-1.6.1.1.txt b/Documentation/RelNotes-1.6.1.1.txt index 10b38e6ec1..5cd1ca9cc6 100644 --- a/Documentation/RelNotes-1.6.1.1.txt +++ b/Documentation/RelNotes-1.6.1.1.txt @@ -4,20 +4,46 @@ GIT v1.6.1.1 Release Notes Fixes since v1.6.1 ------------------ +* "git apply" took file modes from the patch text and updated the mode + bits of the target tree even when the patch was not about mode changes. + +* "git checkout $tree" did not trigger an error. + +* "git commit" tried to remove COMMIT_EDITMSG from the work tree by mistake. + * "git describe --all" complained when a commit is described with a tag, which was nonsense. +* "git fsck branch" did not work as advertised; instead it behaved the same + way as "git fsck". + * "git log --pretty=format:%s" did not handle a multi-line subject the same way as built-in log listers (i.e. shortlog, --pretty=oneline, etc.) * "git daemon", and "git merge-file" are more careful when freopen fails and barf, instead of going on and writing to unopened filehandle. -Other documentation fixes. +* "git http-push" did not like some RFC 4918 compliant DAV server + responses. + +* "git merge -s recursive" mistakenly overwritten an untracked file in the + work tree upon delete/modify conflict. + +* "git merge -s recursive" didn't leave the index unmerged for entries with + rename/delete conflictd. + +* "git merge -s recursive" clobbered untracked files in the work tree. + +* "git mv -k" with more than one errorneous paths misbehaved. + +* "git rebase -i" issued an unnecessary error message upon a user error of + marking the first commit to be "squash"ed. + +Other documentation updates. --- exec >/var/tmp/1 -O=v1.6.1-15-ga9e67c8 +O=v1.6.1-60-g78f111e echo O=$(git describe maint) git shortlog --no-merges $O..maint diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt index 1a80626781..296804301f 100644 --- a/Documentation/RelNotes-1.6.2.txt +++ b/Documentation/RelNotes-1.6.2.txt @@ -10,8 +10,33 @@ Updates since v1.6.1 (performance) +* pack-objects autodetects the number of CPUs available and uses threaded + version. + (usability, bells and whistles) +* "git-add -p" learned 'g'oto action to jump directly to a hunk. + +* git-cherry defaults to HEAD when the <upstream> argument is not given. + +* git-cvsserver can be told not to add extra "via git-CVS emulator" to the + commit log message it serves via gitcvs.commitmsgannotation configuration. + +* git-diff learned a new option --inter-hunk-context to coalesce close + hunks together and show context between them. + +* git-filter-branch learned --prune-empty option that discards commits + that do not change the contents. + +* git-ls-tree learned --full-tree option that shows the path in full + regardless of where in the work tree hierarchy the command was started. + +* git-mergetool learned -y(--no-prompt) option to disable prompting. + +* "git-reset --merge" is a new mode that works similar to the way + "git checkout" switches branches, taking the local changes while + switching to another commit. + (internal) @@ -21,8 +46,20 @@ Fixes since v1.6.1 All of the fixes in v1.6.1.X maintenance series are included in this release, unless otherwise noted. +* "git-add sub/file" when sub is a submodule incorrectly added the path to + the superproject. + +* git-bundle did not exclude annotated tags even when a range given from the + command line wanted to. + +* git-grep did not work correctly for index entries with assume-unchanged bit. + +* branch switching and merges had a silly bug that did not validate + the correct directory when making sure an existing subdirectory is + clean. + -- exec >/var/tmp/1 -O=v1.6.1 +O=v1.6.1-134-ge98c6a1 echo O=$(git describe master) git shortlog --no-merges $O..master ^maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 52786c7df5..6b3ac5aa90 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -702,7 +702,9 @@ gc.packrefs:: gc.pruneexpire:: When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'. - Override the grace period with this config variable. + Override the grace period with this config variable. The value + "now" may be used to disable this grace period and always prune + unreachable objects immediately. gc.reflogexpire:: 'git-reflog expire' removes reflog entries older than @@ -723,6 +725,10 @@ gc.rerereunresolved:: kept for this many days when 'git-rerere gc' is run. The default is 15 days. See linkgit:git-rerere[1]. +gitcvs.commitmsgannotation:: + Append this string to each commit message. Set to empty string + to disable this feature. Defaults to "via git-CVS emulator". + gitcvs.enabled:: Whether the CVS server interface is enabled for this repository. See linkgit:git-cvsserver[1]. @@ -1044,6 +1050,16 @@ mergetool.keepBackup:: is set to `false` then this file is not preserved. Defaults to `true` (i.e. keep the backup files). +mergetool.keepTemporaries:: + When invoking a custom merge tool, git uses a set of temporary + files to pass to the tool. If the tool returns an error and this + variable is set to `true`, then these temporary files will be + preserved, otherwise they will be removed after the tool has + exited. Defaults to `false`. + +mergetool.prompt:: + Prompt before each invocation of the merge resolution program. + pack.window:: The size of the window used by linkgit:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b432d2518a..43793d7500 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -116,7 +116,7 @@ endif::git-format-patch[] --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object name in diff-raw format output and diff-tree header - lines, show only handful hexdigits prefix. This is + lines, show only a partial prefix. This is independent of --full-index option above, which controls the diff-patch output format. Non default number of digits can be specified with --abbrev=<n>. @@ -205,6 +205,10 @@ endif::git-format-patch[] differences even if one line has whitespace where the other line has none. +--inter-hunk-context=<lines>:: + Show the context between diff hunks, up to the specified number + of lines, thereby fusing hunks that are close to each other. + --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index 74d14c4e7f..7deefdae8f 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -7,7 +7,7 @@ git-cherry - Find commits not merged upstream SYNOPSIS -------- -'git cherry' [-v] <upstream> [<head>] [<limit>] +'git cherry' [-v] [<upstream> [<head> [<limit>]]] DESCRIPTION ----------- @@ -51,6 +51,7 @@ OPTIONS <upstream>:: Upstream branch to compare against. + Defaults to the first tracked remote branch, if available. <head>:: Working branch; defaults to HEAD. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 3d79f05995..a99b4ef943 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -87,7 +87,7 @@ With something like git.git current tree, I get: v1.0.4-14-g2414721 i.e. the current head of my "parent" branch is based on v1.0.4, -but since it has a handful commits on top of that, +but since it has a few commits on top of that, describe has added the number of additional commits ("14") and an abbreviated object name for the commit itself ("2414721") at the end. diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt index 5c8c1d95a8..c526141564 100644 --- a/Documentation/git-diff-files.txt +++ b/Documentation/git-diff-files.txt @@ -21,7 +21,10 @@ OPTIONS ------- include::diff-options.txt[] --1 -2 -3 or --base --ours --theirs, and -0:: +-1 --base:: +-2 --ours:: +-3 --theirs:: +-0:: Diff against the "base" version, "our branch" or "their branch" respectively. With these options, diffs for merged entries are not shown. diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index fed6de6a7f..451950bab6 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -122,6 +122,10 @@ You can use the 'map' convenience function in this filter, and other convenience functions, too. For example, calling 'skip_commit "$@"' will leave out the current commit (but not its changes! If you want that, use 'git-rebase' instead). ++ +You can also use the 'git_commit_non_empty_tree "$@"' instead of +'git commit-tree "$@"' if you don't wish to keep commits with a single parent +and that makes no change to the tree. --tag-name-filter <command>:: This is the filter for rewriting tag names. When passed, @@ -151,6 +155,16 @@ to other tags will be rewritten to point to the underlying commit. The result will contain that directory (and only that) as its project root. +--prune-empty:: + Some kind of filters will generate empty commits, that left the tree + untouched. This switch allow git-filter-branch to ignore such + commits. Though, this switch only applies for commits that have one + and only one parent, it will hence keep merges points. Also, this + option is not compatible with the use of '--commit-filter'. Though you + just need to use the function 'git_commit_non_empty_tree "$@"' instead + of the 'git commit-tree "$@"' idiom in your commit filter to make that + happen. + --original <namespace>:: Use this option to set the namespace where the original commits will be stored. The default value is 'refs/original'. diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 9f85d60b5f..057a021eb5 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -126,7 +126,7 @@ OPTIONS --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object - lines, show only handful hexdigits prefix. + lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. \--:: diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 4c7262f1cd..f68e5c5c1a 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git ls-tree' [-d] [-r] [-t] [-l] [-z] - [--name-only] [--name-status] [--full-name] [--abbrev=[<n>]] + [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev=[<n>]] <tree-ish> [paths...] DESCRIPTION @@ -30,6 +30,8 @@ in the current working directory. Note that: 'sub/dir' in 'HEAD'). You don't want to give a tree that is not at the root level (e.g. 'git ls-tree -r HEAD:sub dir') in this case, as that would result in asking for 'sub/sub/dir' in the 'HEAD' commit. + However, the current working directory can be ignored by passing + --full-tree option. OPTIONS ------- @@ -59,13 +61,17 @@ OPTIONS --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object - lines, show only handful hexdigits prefix. + lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. --full-name:: Instead of showing the path names relative to the current working directory, show the full path names. +--full-tree:: + Do not limit the listing to the current working directory. + Implies --full-name. + paths:: When paths are given, show them (note that this isn't really raw pathnames, but rather a list of patterns to match). Otherwise diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 602e7c6d3b..5d3c632872 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -7,7 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts SYNOPSIS -------- -'git mergetool' [--tool=<tool>] [<file>]... +'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]... DESCRIPTION ----------- @@ -22,7 +22,8 @@ with merge conflicts. OPTIONS ------- --t or --tool=<tool>:: +-t <tool>:: +--tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff @@ -60,6 +61,16 @@ variable `mergetool.<tool>.trustExitCode` can be set to `true`. Otherwise, 'git-mergetool' will prompt the user to indicate the success of the resolution after the custom tool has exited. +-y:: +--no-prompt:: + Don't prompt before each invocation of the merge resolution + program. + +--prompt:: + Prompt before each invocation of the merge resolution program. + This is the default behaviour; the option is provided to + override any configuration settings. + Author ------ Written by Theodore Y Ts'o <tytso@mit.edu> diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 6150b1b959..3321966c6b 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -86,14 +86,12 @@ nor in any Push line of the corresponding remotes file---see below). line. --receive-pack=<git-receive-pack>:: +--exec=<git-receive-pack>:: Path to the 'git-receive-pack' program on the remote end. Sometimes useful when pushing to a remote repository over ssh, and you do not have the program in a directory on the default $PATH. ---exec=<git-receive-pack>:: - Same as \--receive-pack=<git-receive-pack>. - -f:: --force:: Usually, the command refuses to update a remote ref that is diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index c8ad86a56f..3d6d429e5e 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] - [-s <strategy> | --strategy=<strategy>] [--no-verify] - [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] - [--onto <newbase>] <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] [--onto <newbase>] + <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] --onto <newbase> + --root [<branch>] + 'git rebase' --continue | --skip | --abort DESCRIPTION @@ -22,7 +23,8 @@ it remains on the current branch. All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set -of commits that would be shown by `git log <upstream>..HEAD`. +of commits that would be shown by `git log <upstream>..HEAD` (or +`git log HEAD`, if --root is specified). The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as @@ -255,6 +257,15 @@ OPTIONS --preserve-merges:: Instead of ignoring merges, try to recreate them. +--root:: + Rebase all commits reachable from <branch>, instead of + limiting them with an <upstream>. This allows you to rebase + the root commit(s) on a branch. Must be used with --onto, and + will skip changes already contained in <newbase> (instead of + <upstream>). When used together with --preserve-merges, 'all' + root commits will be rewritten to have <newbase> as parent + instead. + include::merge-strategies.txt[] NOTES diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index b69846e522..ff4aeff4e6 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -34,6 +34,7 @@ The --bcc option must be repeated for each user you want on the bcc list. --cc:: Specify a starting "Cc:" value for each email. + Default is the value of 'sendemail.cc'. + The --cc option must be repeated for each user you want on the cc list. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 046ab3542b..e44f543025 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -70,7 +70,7 @@ OPTIONS -m <msg>:: Use the given tag message (instead of prompting). - If multiple `-m` options are given, there values are + If multiple `-m` options are given, their values are concatenated as separate paragraphs. Implies `-a` if none of `-a`, `-s`, or `-u <key-id>` is given. @@ -207,7 +207,7 @@ determines who are interested in whose tags. A one-shot pull is a sign that a commit history is now crossing the boundary between one circle of people (e.g. "people who are -primarily interested in networking part of the kernel") who may +primarily interested in the networking part of the kernel") who may have their own set of tags (e.g. "this is the third release candidate from the networking group to be proposed for general consumption with 2.6.21 release") to another circle of people diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index e4dd5518c8..7ba5e589d7 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -1243,10 +1243,10 @@ $ git ls-files --stage ------------ In our example of only two files, we did not have unchanged -files so only 'example' resulted in collapsing, but in real-life -large projects, only small number of files change in one commit, -and this 'collapsing' tends to trivially merge most of the paths -fairly quickly, leaving only a handful the real changes in non-zero +files so only 'example' resulted in collapsing. But in real-life +large projects, when only a small number of files change in one commit, +this 'collapsing' tends to trivially merge most of the paths +fairly quickly, leaving only a handful of real changes in non-zero stages. To look at only non-zero stages, use `\--unmerged` flag: diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index cfdae1efa2..1fd512bca2 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -15,7 +15,7 @@ DESCRIPTION Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When -'git-init' is run, a handful example hooks are copied in the +'git-init' is run, a handful of example hooks are copied into the `hooks` directory of the new repository, but by default they are all disabled. To enable a hook, rename it by removing its `.sample` suffix. @@ -90,7 +90,7 @@ This hook is invoked by 'git-commit' right after preparing the default log message, and before the editor is started. It takes one to three parameters. The first is the name of the file -that the commit log message. The second is the source of the commit +that contains the commit log message. The second is the source of the commit message, and can be: `message` (if a `-m` or `-F` option was given); `template` (if a `-t` option was given or the configuration option `commit.template` is set); `merge` (if the diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 7892244ef1..458fafdb2c 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -590,7 +590,7 @@ list. When the history has lines of development that diverged and then merged back together, the order in which 'git-log' presents those commits is meaningless. -Most projects with multiple contributors (such as the linux kernel, +Most projects with multiple contributors (such as the Linux kernel, or git itself) have frequent merges, and 'gitk' does a better job of visualizing their history. For example, @@ -642,7 +642,7 @@ digressions that may be interesting at this point are: * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert series of git commits into emailed patches, and vice versa, - useful for projects such as the linux kernel which rely heavily + useful for projects such as the Linux kernel which rely heavily on emailed patches. * linkgit:git-bisect[1]: When there is a regression in your diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index d214d4bf9d..74a1c0c4ba 100644 --- a/Documentation/howto/rebase-from-internal-branch.txt +++ b/Documentation/howto/rebase-from-internal-branch.txt @@ -27,7 +27,7 @@ the kind of task StGIT is designed to do. I just have done a simpler one, this time using only the core GIT tools. -I had a handful commits that were ahead of master in pu, and I +I had a handful of commits that were ahead of master in pu, and I wanted to add some documentation bypassing my usual habit of placing new things in pu first. At the beginning, the commit ancestry graph looked like this: diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 6d66c74cc1..5f21efe407 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -10,7 +10,7 @@ configuration (see linkgit:git-config[1]). --abbrev-commit:: Instead of showing the full 40-byte hexadecimal commit object - name, show only handful hexdigits prefix. Non default number of + name, show only a partial prefix. Non default number of digits can be specified with "--abbrev=<n>" (which also modifies diff output, if it is displayed). + diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index a8ee2fe6a1..9a4e3ea92c 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -133,8 +133,10 @@ Functions * Adding data to the buffer -NOTE: All of these functions in this section will grow the buffer as - necessary. +NOTE: All of the functions in this section will grow the buffer as necessary. +If they fail for some reason other than memory shortage and the buffer hadn't +been allocated before (i.e. the `struct strbuf` was set to `STRBUF_INIT`), +then they will free() it. `strbuf_addch`:: @@ -235,6 +237,11 @@ same behaviour as well. Read the contents of a file, specified by its path. The third argument can be used to give a hint about the file size, to avoid reallocs. +`strbuf_readlink`:: + + Read the target of a symbolic link, specified by its path. The third + argument can be used to give a hint about the size, to avoid reallocs. + `strbuf_getline`:: Read a line from a FILE* pointer. The second argument specifies the line diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index d4b1e90f94..19f571ae3b 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -59,7 +59,7 @@ project in mind, here are some interesting examples: ------------------------------------------------ # git itself (approx. 10MB download): $ git clone git://git.kernel.org/pub/scm/git/git.git - # the linux kernel (approx. 150MB download): + # the Linux kernel (approx. 150MB download): $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git ------------------------------------------------ @@ -1009,7 +1009,7 @@ $ git init If you have some initial content (say, a tarball): ------------------------------------------------- -$ tar -xzvf project.tar.gz +$ tar xzvf project.tar.gz $ cd project $ git init $ git add . # include everything below ./ in the first commit: @@ -1340,7 +1340,7 @@ These will display all commits which exist only on HEAD or on MERGE_HEAD, and which touch an unmerged file. You may also use linkgit:git-mergetool[1], which lets you merge the -unmerged files using external tools such as emacs or kdiff3. +unmerged files using external tools such as Emacs or kdiff3. Each time you resolve the conflicts in a file and update the index: @@ -101,6 +101,9 @@ Issues of note: Building and installing the info file additionally requires makeinfo and docbook2X. Version 0.8.3 is known to work. + Building and installing the pdf file additionally requires + dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work. + The documentation is written for AsciiDoc 7, but "make ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8. @@ -1307,6 +1307,9 @@ html: info: $(MAKE) -C Documentation info +pdf: + $(MAKE) -C Documentation pdf + TAGS: $(RM) TAGS $(FIND) . -name '*.[hcS]' -print | xargs etags -a @@ -1353,7 +1356,14 @@ endif ### Testing rules -TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X +TEST_PROGRAMS += test-chmtime$X +TEST_PROGRAMS += test-date$X +TEST_PROGRAMS += test-delta$X +TEST_PROGRAMS += test-genrandom$X +TEST_PROGRAMS += test-match-trees$X +TEST_PROGRAMS += test-parse-options$X +TEST_PROGRAMS += test-path-utils$X +TEST_PROGRAMS += test-sha1$X all:: $(TEST_PROGRAMS) @@ -1449,6 +1459,9 @@ install-html: install-info: $(MAKE) -C Documentation install-info +install-pdf: + $(MAKE) -C Documentation install-pdf + quick-install-doc: $(MAKE) -C Documentation quick-install @@ -24,7 +24,7 @@ It was originally written by Linus Torvalds with help of a group of hackers around the net. It is currently maintained by Junio C Hamano. Please read the file INSTALL for installation instructions. -See Documentation/tutorial.txt to get started, then see +See Documentation/gittutorial.txt to get started, then see Documentation/everyday.txt for a useful minimum set of commands, and "man git-commandname" for documentation of each command. CVS users may also want to read Documentation/cvs-migration.txt. diff --git a/builtin-add.c b/builtin-add.c index 719de8b0f2..ac98c8354d 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -68,6 +68,33 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p free(seen); } +static void treat_gitlinks(const char **pathspec) +{ + int i; + + if (!pathspec || !*pathspec) + return; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (S_ISGITLINK(ce->ce_mode)) { + int len = ce_namelen(ce), j; + for (j = 0; pathspec[j]; j++) { + int len2 = strlen(pathspec[j]); + if (len2 <= len || pathspec[j][len] != '/' || + memcmp(ce->name, pathspec[j], len)) + continue; + if (len2 == len + 1) + /* strip trailing slash */ + pathspec[j] = xstrndup(ce->name, len); + else + die ("Path '%s' is in submodule '%.*s'", + pathspec[j], len, ce->name); + } + } + } +} + static void fill_directory(struct dir_struct *dir, const char **pathspec, int ignored_too) { @@ -261,6 +288,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die("index file corrupt"); + treat_gitlinks(pathspec); if (add_new_files) /* This picks up the paths that are not tracked */ diff --git a/builtin-apply.c b/builtin-apply.c index 2811c0fe4d..6d5a60214c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -635,7 +635,7 @@ static int gitdiff_index(const char *line, struct patch *patch) memcpy(patch->new_sha1_prefix, line, len); patch->new_sha1_prefix[len] = 0; if (*ptr == ' ') - patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8); + patch->old_mode = strtoul(ptr+1, NULL, 8); return 0; } @@ -2452,6 +2452,8 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s if (st_mode != patch->old_mode) fprintf(stderr, "warning: %s has type %o, expected %o\n", old_name, st_mode, patch->old_mode); + if (!patch->new_mode) + patch->new_mode = st_mode; return 0; is_new: diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 30d00a6664..8fad19daed 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -137,7 +137,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) break; default: - die("git cat-file: unknown option: %s\n", exp_type); + die("git cat-file: unknown option: %s", exp_type); } if (!buf) diff --git a/builtin-checkout.c b/builtin-checkout.c index c2c05613b6..b5dd9c07b4 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -681,8 +681,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argv++; argc--; + new.name = arg; if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - new.name = arg; setup_branch_path(&new); if (resolve_ref(new.path, rev, 1, NULL)) new.commit = lookup_commit_reference(rev); diff --git a/builtin-clone.c b/builtin-clone.c index 2feac9c5cb..f7e5a7b0a0 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -192,15 +192,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) dir = opendir(src->buf); if (!dir) - die("failed to open %s\n", src->buf); + die("failed to open %s", src->buf); if (mkdir(dest->buf, 0777)) { if (errno != EEXIST) - die("failed to create directory %s\n", dest->buf); + die("failed to create directory %s", dest->buf); else if (stat(dest->buf, &buf)) - die("failed to stat %s\n", dest->buf); + die("failed to stat %s", dest->buf); else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory\n", dest->buf); + die("%s exists and is not a directory", dest->buf); } strbuf_addch(src, '/'); @@ -224,16 +224,16 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (unlink(dest->buf) && errno != ENOENT) - die("failed to unlink %s\n", dest->buf); + die("failed to unlink %s", dest->buf); if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; if (option_local) - die("failed to create link %s\n", dest->buf); + die("failed to create link %s", dest->buf); option_no_hardlinks = 1; } if (copy_file(dest->buf, src->buf, 0666)) - die("failed to copy file to %s\n", dest->buf); + die("failed to copy file to %s", dest->buf); } closedir(dir); } @@ -357,6 +357,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; + int dest_exists; const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; @@ -406,8 +407,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - if (!stat(dir, &buf)) - die("destination directory '%s' already exists.", dir); + dest_exists = !stat(dir, &buf); + if (dest_exists && !is_empty_dir(dir)) + die("destination path '%s' already exists and is not " + "an empty directory.", dir); strbuf_addf(&reflog_msg, "clone: from %s", repo); @@ -431,7 +434,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(work_tree) < 0) die("could not create leading directories of '%s': %s", work_tree, strerror(errno)); - if (mkdir(work_tree, 0755)) + if (!dest_exists && mkdir(work_tree, 0755)) die("could not create work tree dir '%s': %s.", work_tree, strerror(errno)); set_git_work_tree(work_tree); diff --git a/builtin-commit.c b/builtin-commit.c index e88b78f811..2f0b00a174 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -624,7 +624,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (!commitable && !in_merge && !allow_empty && !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix, 0); - unlink(commit_editmsg); return 0; } @@ -866,6 +865,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (wt_status_use_color == -1) wt_status_use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix); index_file = prepare_index(argc, argv, prefix); @@ -945,6 +947,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_config(git_commit_config, NULL); + if (wt_status_use_color == -1) + wt_status_use_color = git_use_color_default; + argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); index_file = prepare_index(argc, argv, prefix); diff --git a/builtin-count-objects.c b/builtin-count-objects.c index ab35b65b07..62fd1f0961 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "dir.h" #include "builtin.h" #include "parse-options.h" @@ -21,9 +22,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, const char *cp; int bad = 0; - if ((ent->d_name[0] == '.') && - (ent->d_name[1] == 0 || - ((ent->d_name[1] == '.') && (ent->d_name[2] == 0)))) + if (is_dot_or_dotdot(ent->d_name)) continue; for (cp = ent->d_name; *cp; cp++) { int ch = *cp; diff --git a/builtin-fast-export.c b/builtin-fast-export.c index 838633808c..e9ee2c79ac 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -497,6 +497,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 1) + usage_with_options (fast_export_usage, options); + /* we handle encodings */ git_config(git_default_config, NULL); diff --git a/builtin-fetch.c b/builtin-fetch.c index 7568163af2..de6f3074b1 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -607,7 +607,7 @@ static void set_option(const char *name, const char *value) { int r = transport_set_option(transport, name, value); if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s\n", + die("Option \"%s\" value \"%s\" is not valid for %s", name, value, transport->url); if (r > 0) warning("Option \"%s\" is ignored for %s\n", diff --git a/builtin-fsck.c b/builtin-fsck.c index 297b2c41c6..aecc8280a0 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "fsck.h" #include "parse-options.h" +#include "dir.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -395,19 +396,12 @@ static void fsck_dir(int i, char *path) while ((de = readdir(dir)) != NULL) { char name[100]; unsigned char sha1[20]; - int len = strlen(de->d_name); - switch (len) { - case 2: - if (de->d_name[1] != '.') - break; - case 1: - if (de->d_name[0] != '.') - break; + if (is_dot_or_dotdot(de->d_name)) continue; - case 38: + if (strlen(de->d_name) == 38) { sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, len+1); + memcpy(name+2, de->d_name, 39); if (get_sha1_hex(name, sha1) < 0) break; add_sha1_list(sha1, DIRENT_SORT_HINT(de)); @@ -628,7 +622,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } heads = 0; - for (i = 1; i < argc; i++) { + for (i = 0; i < argc; i++) { const char *arg = argv[i]; if (!get_sha1(arg, head_sha1)) { struct object *obj = lookup_object(head_sha1); diff --git a/builtin-gc.c b/builtin-gc.c index 781df601c5..f8eae4adb4 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -188,7 +188,9 @@ static int need_to_gc(void) * there is no need. */ if (too_many_packs()) - append_option(argv_repack, "-A", MAX_ADD); + append_option(argv_repack, + !strcmp(prune_expire, "now") ? "-a" : "-A", + MAX_ADD); else if (!too_many_loose_objects()) return 0; @@ -243,7 +245,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) "run \"git gc\" manually. See " "\"git help gc\" for more information.\n"); } else - append_option(argv_repack, "-A", MAX_ADD); + append_option(argv_repack, + !strcmp(prune_expire, "now") ? "-a" : "-A", + MAX_ADD); if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) return error(FAILED_RUN, argv_pack_refs[0]); diff --git a/builtin-grep.c b/builtin-grep.c index 624f86e287..bebf15cd6f 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -20,6 +20,8 @@ #endif #endif +static int builtin_grep; + /* * git grep pathspecs are somewhat different from diff-tree pathspecs; * pathname wildcards are allowed. @@ -389,7 +391,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) * we grep through the checked-out files. It tends to * be a lot more optimized */ - if (!cached) { + if (!cached && !builtin_grep) { hit = external_grep(opt, paths, cached); if (hit >= 0) return hit; @@ -402,7 +404,12 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) continue; if (!pathspec_matches(paths, ce->name)) continue; - if (cached) { + /* + * If CE_VALID is on, we assume worktree file and its cache entry + * are identical, even if worktree file has been modified, so use + * cache version instead + */ + if (cached || (ce->ce_flags & CE_VALID)) { if (ce_stage(ce)) continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); @@ -545,6 +552,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) cached = 1; continue; } + if (!strcmp("--no-ext-grep", arg)) { + builtin_grep = 1; + continue; + } if (!strcmp("-a", arg) || !strcmp("--text", arg)) { opt.binary = GREP_BINARY_TEXT; diff --git a/builtin-init-db.c b/builtin-init-db.c index d30c3fe2ca..ee3911f8ee 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -29,7 +29,7 @@ static void safe_create_dir(const char *dir, int share) } } else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group\n", dir); + die("Could not make %s writable by group", dir); } static void copy_templates_1(char *path, int baselen, diff --git a/builtin-log.c b/builtin-log.c index 99d1137b08..c7aa48e748 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -16,6 +16,7 @@ #include "patch-ids.h" #include "run-command.h" #include "shortlog.h" +#include "remote.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -249,22 +250,13 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) static void show_tagger(char *buf, int len, struct rev_info *rev) { - char *email_end, *p; - unsigned long date; - int tz; + struct strbuf out = STRBUF_INIT; - email_end = memchr(buf, '>', len); - if (!email_end) - return; - p = ++email_end; - while (isspace(*p)) - p++; - date = strtoul(p, &p, 10); - while (isspace(*p)) - p++; - tz = (int)strtol(p, NULL, 10); - printf("Tagger: %.*s\nDate: %s\n", (int)(email_end - buf), buf, - show_date(date, tz, rev->date_mode)); + pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, + git_log_output_encoding ? + git_log_output_encoding: git_commit_encoding); + printf("%s\n", out.buf); + strbuf_release(&out); } static int show_object(const unsigned char *sha1, int show_tag_object, @@ -824,7 +816,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); endpos = strchr(committer, '>'); if (!endpos) - die("bogus committer info %s\n", committer); + die("bogus committer info %s", committer); add_signoff = xmemdupz(committer, endpos - committer + 1); } else if (!strcmp(argv[i], "--attach")) { @@ -944,6 +936,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * get_revision() to do the usual traversal. */ } + + /* + * We cannot move this anywhere earlier because we do want to + * know if --root was given explicitly from the comand line. + */ + rev.show_root_diff = 1; + if (cover_letter) { /* remember the range */ int i; @@ -1070,13 +1069,14 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) } static const char cherry_usage[] = -"git cherry [-v] <upstream> [<head>] [<limit>]"; +"git cherry [-v] [<upstream> [<head> [<limit>]]]"; int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct patch_ids ids; struct commit *commit; struct commit_list *list = NULL; + struct branch *current_branch; const char *upstream; const char *head = "HEAD"; const char *limit = NULL; @@ -1099,7 +1099,17 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) upstream = argv[1]; break; default: - usage(cherry_usage); + current_branch = branch_get(NULL); + if (!current_branch || !current_branch->merge + || !current_branch->merge[0] + || !current_branch->merge[0]->dst) { + fprintf(stderr, "Could not find a tracked" + " remote branch, please" + " specify <upstream> manually.\n"); + usage(cherry_usage); + } + + upstream = current_branch->merge[0]->dst; } init_revisions(&revs, prefix); diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index cb61717685..5b63e6eada 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -23,7 +23,7 @@ static int chomp_prefix; static const char *ls_tree_prefix; static const char ls_tree_usage[] = - "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]"; + "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] <tree-ish> [path...]"; static int show_recursive(const char *base, int baselen, const char *pathname) { @@ -156,6 +156,11 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) chomp_prefix = 0; break; } + if (!strcmp(argv[1]+2, "full-tree")) { + ls_tree_prefix = prefix = NULL; + chomp_prefix = 0; + break; + } if (!prefixcmp(argv[1]+2, "abbrev=")) { abbrev = strtoul(argv[1]+9, NULL, 10); if (abbrev && abbrev < MINIMUM_ABBREV) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index e890f7a6d1..f7c8c08b32 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -494,7 +494,7 @@ static void convert_to_utf8(struct strbuf *line, const char *charset) return; out = reencode_string(line->buf, metainfo_charset, charset); if (!out) - die("cannot convert from %s to %s\n", + die("cannot convert from %s to %s", charset, metainfo_charset); strbuf_attach(line, out, strlen(out), strlen(out)); } diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 6b534c1a66..703045bfc8 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) } if (argc < 4) - die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); + die("Usage: %s <base>... -- <head> <remote> ...", argv[0]); for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--")) diff --git a/builtin-mv.c b/builtin-mv.c index 4f65b5ae9b..bce9959293 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -192,6 +192,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) memmove(destination + i, destination + i + 1, (argc - i) * sizeof(char *)); + i--; } } else die ("%s, source=%s, destination=%s", diff --git a/builtin-prune.c b/builtin-prune.c index 7b4ec80e62..545e9c1f94 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "dir.h" static const char * const prune_usage[] = { "git prune [-n] [-v] [--expire <time>] [--] [<head>...]", @@ -61,19 +62,12 @@ static int prune_dir(int i, char *path) while ((de = readdir(dir)) != NULL) { char name[100]; unsigned char sha1[20]; - int len = strlen(de->d_name); - switch (len) { - case 2: - if (de->d_name[1] != '.') - break; - case 1: - if (de->d_name[0] != '.') - break; + if (is_dot_or_dotdot(de->d_name)) continue; - case 38: + if (strlen(de->d_name) == 38) { sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, len+1); + memcpy(name+2, de->d_name, 39); if (get_sha1_hex(name, sha1) < 0) break; diff --git a/builtin-rerere.c b/builtin-rerere.c index d4dec6b715..bd8fc77a7a 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "dir.h" #include "string-list.h" #include "rerere.h" #include "xdiff/xdiff.h" @@ -59,17 +60,15 @@ static void garbage_collect(struct string_list *rr) git_config(git_rerere_gc_config, NULL); dir = opendir(git_path("rr-cache")); while ((e = readdir(dir))) { - const char *name = e->d_name; - if (name[0] == '.' && - (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) + if (is_dot_or_dotdot(e->d_name)) continue; - then = rerere_created_at(name); + then = rerere_created_at(e->d_name); if (!then) continue; - cutoff = (has_resolution(name) + cutoff = (has_resolution(e->d_name) ? cutoff_resolve : cutoff_noresolve); if (then < now - cutoff * 86400) - string_list_append(name, &to_remove); + string_list_append(e->d_name, &to_remove); } for (i = 0; i < to_remove.nr; i++) unlink_rr_item(to_remove.items[i].string); diff --git a/builtin-shortlog.c b/builtin-shortlog.c index d03f14fdad..5f9f3f09b1 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -29,6 +29,9 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } +const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator); + static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) @@ -36,11 +39,11 @@ static void insert_one_record(struct shortlog *log, const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; - struct string_list *onelines; char namebuf[1024]; size_t len; const char *eol; const char *boemail, *eoemail; + struct strbuf subject = STRBUF_INIT; boemail = strchr(author, '<'); if (!boemail) @@ -68,12 +71,9 @@ static void insert_one_record(struct shortlog *log, snprintf(namebuf + len, room, " %.*s", maillen, boemail); } - buffer = xstrdup(namebuf); - item = string_list_insert(buffer, &log->list); + item = string_list_insert(namebuf, &log->list); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); - else - free(buffer); /* Skip any leading whitespace, including any blank lines. */ while (*oneline && isspace(*oneline)) @@ -89,9 +89,8 @@ static void insert_one_record(struct shortlog *log, while (*oneline && isspace(*oneline) && *oneline != '\n') oneline++; len = eol - oneline; - while (len && isspace(oneline[len-1])) - len--; - buffer = xmemdupz(oneline, len); + format_subject(&subject, oneline, " "); + buffer = strbuf_detach(&subject, NULL); if (dot3) { int dot3len = strlen(dot3); @@ -104,16 +103,7 @@ static void insert_one_record(struct shortlog *log, } } - onelines = item->util; - if (onelines->nr >= onelines->alloc) { - onelines->alloc = alloc_nr(onelines->nr); - onelines->items = xrealloc(onelines->items, - onelines->alloc - * sizeof(struct string_list_item)); - } - - onelines->items[onelines->nr].util = NULL; - onelines->items[onelines->nr++].string = buffer; + string_list_append(buffer, item->util); } static void read_from_stdin(struct shortlog *log) @@ -323,7 +313,7 @@ void shortlog_output(struct shortlog *log) } onelines->strdup_strings = 1; - string_list_clear(onelines, 1); + string_list_clear(onelines, 0); free(onelines); log->list.items[i].util = NULL; } diff --git a/builtin-update-index.c b/builtin-update-index.c index 65d5775107..5604977505 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -486,7 +486,7 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { if (read_ref("HEAD", head_sha1)) - die("No HEAD -- no initial commit yet?\n"); + die("No HEAD -- no initial commit yet?"); if (read_ref("MERGE_HEAD", merge_head_sha1)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); @@ -167,6 +167,32 @@ int list_bundle_refs(struct bundle_header *header, int argc, const char **argv) return list_refs(&header->references, argc, argv); } +static int is_tag_in_date_range(struct object *tag, struct rev_info *revs) +{ + unsigned long size; + enum object_type type; + char *buf, *line, *lineend; + unsigned long date; + + if (revs->max_age == -1 && revs->min_age == -1) + return 1; + + buf = read_sha1_file(tag->sha1, &type, &size); + if (!buf) + return 1; + line = memmem(buf, size, "\ntagger ", 8); + if (!line++) + return 1; + lineend = memchr(line, buf + size - line, '\n'); + line = memchr(line, lineend ? lineend - line : buf + size - line, '>'); + if (!line++) + return 1; + date = strtoul(line, NULL, 10); + free(buf); + return (revs->max_age == -1 || revs->max_age < date) && + (revs->min_age == -1 || revs->min_age > date); +} + int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv) { @@ -255,6 +281,12 @@ int create_bundle(struct bundle_header *header, const char *path, flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; + if (e->item->type == OBJ_TAG && + !is_tag_in_date_range(e->item, &revs)) { + e->item->flags |= UNINTERESTING; + continue; + } + /* * Make sure the refs we wrote out is correct; --max-count and * other limiting options could have prevented all the tips @@ -631,9 +631,6 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); -/* just like read_sha1_file(), but non fatal in presence of bad objects */ -extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); - /* global flag to enable extra checks when accessing packed objects */ extern int do_check_packed_object_crc; diff --git a/configure.ac b/configure.ac index 8821b5080a..0a5fc8c6f6 100644 --- a/configure.ac +++ b/configure.ac @@ -127,7 +127,7 @@ else SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/" AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no]) - LDFLAGS="${SAVE_LD_FLAGS}" + LDFLAGS="${SAVE_LDFLAGS}" ]) if test "$ld_wl_rpath" = "yes"; then AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,]) @@ -136,7 +136,7 @@ else SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -rpath /" AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no]) - LDFLAGS="${SAVE_LD_FLAGS}" + LDFLAGS="${SAVE_LDFLAGS}" ]) if test "$ld_rpath" = "yes"; then AC_SUBST(CC_LD_DYNPATH, [-rpath]) @@ -315,7 +315,7 @@ static int git_tcp_connect_sock(char *host, int flags) /* Not numeric */ struct servent *se = getservbyname(port,"tcp"); if ( !se ) - die("Unknown port %s\n", port); + die("Unknown port %s", port); nport = se->s_port; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e00454983e..ec701e8069 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,3 +1,4 @@ +#!bash # # bash completion support for core Git. # @@ -50,9 +51,11 @@ case "$COMP_WORDBREAKS" in *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac +# __gitdir accepts 0 or 1 arguments (i.e., location) +# returns location of .git repo __gitdir () { - if [ -z "$1" ]; then + if [ -z "${1-}" ]; then if [ -n "$__git_dir" ]; then echo "$__git_dir" elif [ -d .git ]; then @@ -67,6 +70,8 @@ __gitdir () fi } +# __git_ps1 accepts 0 or 1 arguments (i.e., format string) +# returns text to add to bash PS1 prompt (includes branch name) __git_ps1 () { local g="$(git rev-parse --git-dir 2>/dev/null)" @@ -111,7 +116,7 @@ __git_ps1 () fi fi - if [ -n "$1" ]; then + if [ -n "${1-}" ]; then printf "$1" "${b##refs/heads/}$r" else printf " (%s)" "${b##refs/heads/}$r" @@ -119,6 +124,7 @@ __git_ps1 () fi } +# __gitcomp_1 requires 2 arguments __gitcomp_1 () { local c IFS=' '$'\t'$'\n' @@ -131,6 +137,8 @@ __gitcomp_1 () done } +# __gitcomp accepts 1, 2, 3, or 4 arguments +# generates completion reply with compgen __gitcomp () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -143,22 +151,23 @@ __gitcomp () ;; *) local IFS=$'\n' - COMPREPLY=($(compgen -P "$2" \ - -W "$(__gitcomp_1 "$1" "$4")" \ + COMPREPLY=($(compgen -P "${2-}" \ + -W "$(__gitcomp_1 "${1-}" "${4-}")" \ -- "$cur")) ;; esac } +# __git_heads accepts 0 or 1 arguments (to pass to __gitdir) __git_heads () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/heads return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -168,15 +177,16 @@ __git_heads () done } +# __git_tags accepts 0 or 1 arguments (to pass to __gitdir) __git_tags () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/tags return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -186,9 +196,10 @@ __git_tags () done } +# __git_refs accepts 0 or 1 arguments (to pass to __gitdir) __git_refs () { - local i is_hash=y dir="$(__gitdir "$1")" + local i is_hash=y dir="$(__gitdir "${1-}")" local cur="${COMP_WORDS[COMP_CWORD]}" format refs if [ -d "$dir" ]; then case "$cur" in @@ -218,6 +229,7 @@ __git_refs () done } +# __git_refs2 requires 1 argument (to pass to __git_refs) __git_refs2 () { local i @@ -226,6 +238,7 @@ __git_refs2 () done } +# __git_refs_remotes requires 1 argument (to pass to ls-remote) __git_refs_remotes () { local cmd i is_hash=y @@ -470,6 +483,7 @@ __git_aliases () done } +# __git_aliased_command requires 1 argument __git_aliased_command () { local word cmdline=$(git --git-dir="$(__gitdir)" \ @@ -482,6 +496,7 @@ __git_aliased_command () done } +# __git_find_subcommand requires 1 argument __git_find_subcommand () { local word subcommand c=1 @@ -563,7 +578,7 @@ _git_add () --*) __gitcomp " --interactive --refresh --patch --update --dry-run - --ignore-errors + --ignore-errors --intent-to-add " return esac @@ -628,7 +643,6 @@ _git_branch () done case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; --*) __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev @@ -776,6 +790,7 @@ _git_diff () --no-ext-diff --no-prefix --src-prefix= --dst-prefix= --base --ours --theirs + --inter-hunk-context= " return ;; @@ -823,6 +838,8 @@ _git_format_patch () --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix= + --inline --suffix= --ignore-if-in-upstream + --subject-prefix= " return ;; @@ -930,6 +947,8 @@ _git_ls_tree () __git_complete_file } +__git_log_pretty_formats="oneline short medium full fuller email raw format:" + _git_log () { __git_has_doubledash && return @@ -937,8 +956,7 @@ _git_log () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats " "" "${cur##--pretty=}" return ;; @@ -967,6 +985,7 @@ _git_log () --color-words --walk-reflogs --parents --children --full-history --merge + --inter-hunk-context= " return ;; @@ -1403,7 +1422,7 @@ _git_reset () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - __gitcomp "--mixed --hard --soft" + __gitcomp "--merge --mixed --hard --soft" return ;; esac @@ -1465,8 +1484,7 @@ _git_show () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats " "" "${cur##--pretty=}" return ;; @@ -1672,7 +1690,6 @@ _git () if [ -z "$command" ]; then case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; --*) __gitcomp " --paginate --no-pager @@ -1736,6 +1753,7 @@ _git () show) _git_show ;; show-branch) _git_show_branch ;; stash) _git_stash ;; + stage) _git_add ;; submodule) _git_submodule ;; svn) _git_svn ;; tag) _git_tag ;; @@ -1763,13 +1781,16 @@ _gitk () __git_complete_revlist } -complete -o default -o nospace -F _git git -complete -o default -o nospace -F _gitk gitk +complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \ + || complete -o default -o nospace -F _git git +complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \ + || complete -o default -o nospace -F _gitk gitk # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o default -o nospace -F _git git.exe +complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ + || complete -o default -o nospace -F _git git.exe fi diff --git a/contrib/difftool/git-difftool b/contrib/difftool/git-difftool new file mode 100755 index 0000000000..1fc087c5fc --- /dev/null +++ b/contrib/difftool/git-difftool @@ -0,0 +1,74 @@ +#!/usr/bin/env perl +# Copyright (c) 2009 David Aguilar +# +# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible +# git-difftool-helper script. This script exports +# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and +# GIT_NO_PROMPT and GIT_MERGE_TOOL for use by git-difftool-helper. +# Any arguments that are unknown to this script are forwarded to 'git diff'. + +use strict; +use warnings; +use Cwd qw(abs_path); +use File::Basename qw(dirname); + +my $DIR = abs_path(dirname($0)); + + +sub usage +{ + print << 'USAGE'; + +usage: git difftool [--no-prompt] [--tool=tool] ["git diff" options] +USAGE + exit 1; +} + +sub setup_environment +{ + $ENV{PATH} = "$DIR:$ENV{PATH}"; + $ENV{GIT_PAGER} = ''; + $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper'; +} + +sub exe +{ + my $exe = shift; + return defined $ENV{COMSPEC} ? "$exe.exe" : $exe; +} + +sub generate_command +{ + my @command = (exe('git'), 'diff'); + my $skip_next = 0; + my $idx = -1; + for my $arg (@ARGV) { + $idx++; + if ($skip_next) { + $skip_next = 0; + next; + } + if ($arg eq '-t' or $arg eq '--tool') { + usage() if $#ARGV <= $idx; + $ENV{GIT_MERGE_TOOL} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--tool=/) { + $ENV{GIT_MERGE_TOOL} = substr($arg, 7); + next; + } + if ($arg eq '--no-prompt') { + $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; + next; + } + if ($arg eq '-h' or $arg eq '--help') { + usage(); + } + push @command, $arg; + } + return @command +} + +setup_environment(); +exec(generate_command()); diff --git a/contrib/difftool/git-difftool-helper b/contrib/difftool/git-difftool-helper new file mode 100755 index 0000000000..0b266e3603 --- /dev/null +++ b/contrib/difftool/git-difftool-helper @@ -0,0 +1,240 @@ +#!/bin/sh +# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher. +# It supports kdiff3, tkdiff, xxdiff, meld, opendiff, emerge, ecmerge, +# vimdiff, gvimdiff, and custom user-configurable tools. +# This script is typically launched by using the 'git difftool' +# convenience command. +# +# Copyright (c) 2009 David Aguilar + +# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt. +should_prompt () { + ! test -n "$GIT_DIFFTOOL_NO_PROMPT" +} + +# Should we keep the backup .orig file? +keep_backup_mode="$(git config --bool merge.keepBackup || echo true)" +keep_backup () { + test "$keep_backup_mode" = "true" +} + +# This function manages the backup .orig file. +# A backup $MERGED.orig file is created if changes are detected. +cleanup_temp_files () { + if test -n "$MERGED"; then + if keep_backup && test "$MERGED" -nt "$BACKUP"; then + test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig" + else + rm -f -- "$BACKUP" + fi + fi +} + +# This is called when users Ctrl-C out of git-difftool-helper +sigint_handler () { + echo + cleanup_temp_files + exit 1 +} + +# This function prepares temporary files and launches the appropriate +# merge tool. +launch_merge_tool () { + # Merged is the filename as it appears in the work tree + # Local is the contents of a/filename + # Remote is the contents of b/filename + # Custom merge tool commands might use $BASE so we provide it + MERGED="$1" + LOCAL="$2" + REMOTE="$3" + BASE="$1" + ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" + BACKUP="$MERGED.BACKUP.$ext" + + # Create and ensure that we clean up $BACKUP + test -f "$MERGED" && cp -- "$MERGED" "$BACKUP" + trap sigint_handler SIGINT + + # $LOCAL and $REMOTE are temporary files so prompt + # the user with the real $MERGED name before launching $merge_tool. + if should_prompt; then + printf "\nViewing: '$MERGED'\n" + printf "Hit return to launch '%s': " "$merge_tool" + read ans + fi + + # Run the appropriate merge tool command + case "$merge_tool" in + kdiff3) + basename=$(basename "$MERGED") + "$merge_tool_path" --auto \ + --L1 "$basename (A)" \ + --L2 "$basename (B)" \ + -o "$MERGED" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1 + ;; + + tkdiff) + "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE" + ;; + + meld|vimdiff) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + + gvimdiff) + "$merge_tool_path" -f "$LOCAL" "$REMOTE" + ;; + + xxdiff) + "$merge_tool_path" \ + -X \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$REMOTE" + ;; + + opendiff) + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + -merge "$MERGED" | cat + ;; + + ecmerge) + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + --default --mode=merge2 --to="$MERGED" + ;; + + emerge) + "$merge_tool_path" -f emerge-files-command \ + "$LOCAL" "$REMOTE" "$(basename "$MERGED")" + ;; + + *) + if test -n "$merge_tool_cmd"; then + ( eval $merge_tool_cmd ) + fi + ;; + esac + + cleanup_temp_files +} + +# Verifies that mergetool.<tool>.cmd exists +valid_custom_tool() { + merge_tool_cmd="$(git config mergetool.$1.cmd)" + test -n "$merge_tool_cmd" +} + +# Verifies that the chosen merge tool is properly setup. +# Built-in merge tools are always valid. +valid_tool() { + case "$1" in + kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + ;; # happy + *) + if ! valid_custom_tool "$1" + then + return 1 + fi + ;; + esac +} + +# Sets up the merge_tool_path variable. +# This handles the mergetool.<tool>.path configuration. +init_merge_tool_path() { + merge_tool_path=$(git config mergetool."$1".path) + if test -z "$merge_tool_path"; then + case "$1" in + emerge) + merge_tool_path=emacs + ;; + *) + merge_tool_path="$1" + ;; + esac + fi +} + +# Allow the GIT_MERGE_TOOL variable to provide a default value +test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" + +# If not merge tool was specified then use the merge.tool +# configuration variable. If that's invalid then reset merge_tool. +if test -z "$merge_tool"; then + merge_tool=$(git config merge.tool) + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then + echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" + echo >&2 "Resetting to default..." + unset merge_tool + fi +fi + +# Try to guess an appropriate merge tool if no tool has been set. +if test -z "$merge_tool"; then + + # We have a $DISPLAY so try some common UNIX merge tools + if test -n "$DISPLAY"; then + merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff" + # If gnome then prefer meld + if test -n "$GNOME_DESKTOP_SESSION_ID"; then + merge_tool_candidates="meld $merge_tool_candidates" + fi + # If KDE then prefer kdiff3 + if test "$KDE_FULL_SESSION" = "true"; then + merge_tool_candidates="kdiff3 $merge_tool_candidates" + fi + fi + + # $EDITOR is emacs so add emerge as a candidate + if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then + merge_tool_candidates="$merge_tool_candidates emerge" + fi + + # $EDITOR is vim so add vimdiff as a candidate + if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then + merge_tool_candidates="$merge_tool_candidates vimdiff" + fi + + merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" + echo "merge tool candidates: $merge_tool_candidates" + + # Loop over each candidate and stop when a valid merge tool is found. + for i in $merge_tool_candidates + do + init_merge_tool_path $i + if type "$merge_tool_path" > /dev/null 2>&1; then + merge_tool=$i + break + fi + done + + if test -z "$merge_tool" ; then + echo "No known merge resolution program available." + exit 1 + fi + +else + # A merge tool has been set, so verify that it's valid. + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge tool $merge_tool" + exit 1 + fi + + init_merge_tool_path "$merge_tool" + + if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available as '$merge_tool_path'" + exit 1 + fi +fi + + +# Launch the merge tool on each path provided by 'git diff' +while test $# -gt 6 +do + launch_merge_tool "$1" "$2" "$5" + shift 7 +done diff --git a/contrib/difftool/git-difftool.txt b/contrib/difftool/git-difftool.txt new file mode 100644 index 0000000000..3940c7057c --- /dev/null +++ b/contrib/difftool/git-difftool.txt @@ -0,0 +1,104 @@ +git-difftool(1) +=============== + +NAME +---- +git-difftool - compare changes using common merge tools + +SYNOPSIS +-------- +'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options] + +DESCRIPTION +----------- +'git difftool' is a git command that allows you to compare and edit files +between revisions using common merge tools. At its most basic level, +'git difftool' does what 'git mergetool' does but its use is for non-merge +situations such as when preparing commits or comparing changes against +the index. + +'git difftool' is a frontend to 'git diff' and accepts the same +arguments and options. + +See linkgit:git-diff[7] for the full list of supported options. + +OPTIONS +------- +-t <tool>:: +--tool=<tool>:: + Use the merge resolution program specified by <tool>. + Valid merge tools are: + kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff ++ +If a merge resolution program is not specified, 'git difftool' +will use the configuration variable `merge.tool`. If the +configuration variable `merge.tool` is not set, 'git difftool' +will pick a suitable default. ++ +You can explicitly provide a full path to the tool by setting the +configuration variable `mergetool.<tool>.path`. For example, you +can configure the absolute path to kdiff3 by setting +`mergetool.kdiff3.path`. Otherwise, 'git difftool' assumes the +tool is available in PATH. ++ +Instead of running one of the known merge tool programs, +'git difftool' can be customized to run an alternative program +by specifying the command line to invoke in a configuration +variable `mergetool.<tool>.cmd`. ++ +When 'git difftool' is invoked with this tool (either through the +`-t` or `--tool` option or the `merge.tool` configuration variable) +the configured command line will be invoked with the following +variables available: `$LOCAL` is set to the name of the temporary +file containing the contents of the diff pre-image and `$REMOTE` +is set to the name of the temporary file containing the contents +of the diff post-image. `$BASE` is provided for compatibility +with custom merge tool commands and has the same value as `$LOCAL`. + +--no-prompt:: + Do not prompt before launching a merge tool. + +CONFIG VARIABLES +---------------- +merge.tool:: + The default merge tool to use. ++ +See the `--tool=<tool>` option above for more details. + +merge.keepBackup:: + The original, unedited file content can be saved to a file with + a `.orig` extension. Defaults to `true` (i.e. keep the backup files). + +mergetool.<tool>.path:: + Override the path for the given tool. This is useful in case + your tool is not in the PATH. + +mergetool.<tool>.cmd:: + Specify the command to invoke the specified merge tool. ++ +See the `--tool=<tool>` option above for more details. + + +SEE ALSO +-------- +linkgit:git-diff[7]:: + Show changes between commits, commit and working tree, etc + +linkgit:git-mergetool[1]:: + Run merge conflict resolution tools to resolve merge conflicts + +linkgit:git-config[7]:: + Get and set repository or global options + + +AUTHOR +------ +Written by David Aguilar <davvid@gmail.com>. + +Documentation +-------------- +Documentation by David Aguilar and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/contrib/examples/README b/contrib/examples/README new file mode 100644 index 0000000000..6946f3dd2a --- /dev/null +++ b/contrib/examples/README @@ -0,0 +1,3 @@ +These are original scripted implementations, kept primarily for their +reference value to any aspiring plumbing users who want to learn how +pieces can be fit together. diff --git a/contrib/vim/README b/contrib/vim/README index c487346eba..fca1e17251 100644 --- a/contrib/vim/README +++ b/contrib/vim/README @@ -5,11 +5,13 @@ automatically. If you have an older version of vim, you can get the latest syntax files from the vim project: - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/git.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitcommit.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitconfig.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitrebase.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitsendemail.vim + http://ftp.vim.org/pub/vim/runtime/syntax/git.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim + +These files are also available via FTP at the same location. To install: @@ -716,7 +716,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); if (gai) - die("getaddrinfo() failed: %s\n", gai_strerror(gai)); + die("getaddrinfo() failed: %s", gai_strerror(gai)); for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; diff --git a/diff-lib.c b/diff-lib.c index ae96c64ca2..a41e1ec07c 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -61,14 +61,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option) int silent_on_removed = option & DIFF_SILENT_ON_REMOVED; unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED) ? CE_MATCH_RACY_IS_DIRTY : 0); - char symcache[PATH_MAX]; diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/"); if (diff_unmerged_stage < 0) diff_unmerged_stage = 2; entries = active_nr; - symcache[0] = '\0'; for (i = 0; i < entries; i++) { struct stat st; unsigned int oldmode, newmode; @@ -198,11 +196,6 @@ int run_diff_files(struct rev_info *revs, unsigned int option) * diff-index */ -struct oneway_unpack_data { - struct rev_info *revs; - char symcache[PATH_MAX]; -}; - /* A file entry went away or appeared */ static void diff_index_show_file(struct rev_info *revs, const char *prefix, @@ -216,8 +209,7 @@ static void diff_index_show_file(struct rev_info *revs, static int get_stat_data(struct cache_entry *ce, const unsigned char **sha1p, unsigned int *modep, - int cached, int match_missing, - struct oneway_unpack_data *cbdata) + int cached, int match_missing) { const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; @@ -248,25 +240,24 @@ static int get_stat_data(struct cache_entry *ce, return 0; } -static void show_new_file(struct oneway_unpack_data *cbdata, +static void show_new_file(struct rev_info *revs, struct cache_entry *new, int cached, int match_missing) { const unsigned char *sha1; unsigned int mode; - struct rev_info *revs = cbdata->revs; /* * New file in the index: it might actually be different in * the working copy. */ - if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) return; diff_index_show_file(revs, "+", new, sha1, mode); } -static int show_modified(struct oneway_unpack_data *cbdata, +static int show_modified(struct rev_info *revs, struct cache_entry *old, struct cache_entry *new, int report_missing, @@ -274,9 +265,8 @@ static int show_modified(struct oneway_unpack_data *cbdata, { unsigned int mode, oldmode; const unsigned char *sha1; - struct rev_info *revs = cbdata->revs; - if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) { + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, old->sha1, old->ce_mode); @@ -344,8 +334,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct cache_entry *idx, struct cache_entry *tree) { - struct oneway_unpack_data *cbdata = o->unpack_data; - struct rev_info *revs = cbdata->revs; + struct rev_info *revs = o->unpack_data; int match_missing, cached; /* @@ -368,7 +357,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something added to the tree? */ if (!tree) { - show_new_file(cbdata, idx, cached, match_missing); + show_new_file(revs, idx, cached, match_missing); return; } @@ -381,7 +370,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, } /* Show difference between old and new */ - show_modified(cbdata, tree, idx, 1, cached, match_missing); + show_modified(revs, tree, idx, 1, cached, match_missing); } static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) @@ -418,8 +407,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *idx = src[0]; struct cache_entry *tree = src[1]; - struct oneway_unpack_data *cbdata = o->unpack_data; - struct rev_info *revs = cbdata->revs; + struct rev_info *revs = o->unpack_data; if (idx && ce_stage(idx)) skip_same_name(idx, o); @@ -446,7 +434,6 @@ int run_diff_index(struct rev_info *revs, int cached) const char *tree_name; struct unpack_trees_options opts; struct tree_desc t; - struct oneway_unpack_data unpack_cb; mark_merge_entries(); @@ -456,14 +443,12 @@ int run_diff_index(struct rev_info *revs, int cached) if (!tree) return error("bad tree object %s", tree_name); - unpack_cb.revs = revs; - unpack_cb.symcache[0] = '\0'; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.index_only = cached; opts.merge = 1; opts.fn = oneway_diff; - opts.unpack_data = &unpack_cb; + opts.unpack_data = revs; opts.src_index = &the_index; opts.dst_index = NULL; @@ -486,7 +471,6 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) struct cache_entry *last = NULL; struct unpack_trees_options opts; struct tree_desc t; - struct oneway_unpack_data unpack_cb; /* * This is used by git-blame to run diff-cache internally; @@ -515,14 +499,12 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); - unpack_cb.revs = &revs; - unpack_cb.symcache[0] = '\0'; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; opts.fn = oneway_diff; - opts.unpack_data = &unpack_cb; + opts.unpack_data = &revs; opts.src_index = &the_index; opts.dst_index = &the_index; diff --git a/diff-no-index.c b/diff-no-index.c index b60d3455da..60ed17470a 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -173,8 +173,10 @@ void diff_no_index(struct rev_info *revs, /* Were we asked to do --no-index explicitly? */ for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--")) - return; + if (!strcmp(argv[i], "--")) { + i++; + break; + } if (!strcmp(argv[i], "--no-index")) no_index = 1; if (argv[i][0] != '-') @@ -198,13 +200,6 @@ void diff_no_index(struct rev_info *revs, die("git diff %s takes two paths", no_index ? "--no-index" : "[--no-index]"); - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&revs->diffopt, EXIT_WITH_STATUS)) - setup_pager(); - diff_setup(&revs->diffopt); if (!revs->diffopt.output_format) revs->diffopt.output_format = DIFF_FORMAT_PATCH; @@ -212,8 +207,12 @@ void diff_no_index(struct rev_info *revs, int j; if (!strcmp(argv[i], "--no-index")) i++; - else if (!strcmp(argv[1], "-q")) + else if (!strcmp(argv[i], "-q")) { options |= DIFF_SILENT_ON_REMOVED; + i++; + } + else if (!strcmp(argv[i], "--")) + i++; else { j = diff_opt_parse(&revs->diffopt, argv + i, argc - i); if (!j) @@ -222,6 +221,13 @@ void diff_no_index(struct rev_info *revs, } } + /* + * If the user asked for our exit code then don't start a + * pager or we would end up reporting its exit code instead. + */ + if (!DIFF_OPT_TST(&revs->diffopt, EXIT_WITH_STATUS)) + setup_pager(); + if (prefix) { int len = strlen(prefix); @@ -1469,6 +1469,7 @@ static void builtin_diff(const char *name_a, ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; if (pe) xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); @@ -2039,7 +2040,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) if (lstat(one->path, &st) < 0) die("stat %s", one->path); if (index_path(one->sha1, one->path, &st, 0)) - die("cannot hash %s\n", one->path); + die("cannot hash %s", one->path); } } else @@ -2538,6 +2539,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->b_prefix = arg + 13; else if (!strcmp(arg, "--no-prefix")) options->a_prefix = options->b_prefix = ""; + else if (opt_arg(arg, '\0', "inter-hunk-context", + &options->interhunkcontext)) + ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); options->close_file = 1; @@ -78,6 +78,7 @@ struct diff_options { const char *a_prefix, *b_prefix; unsigned flags; int context; + int interhunkcontext; int break_opt; int detect_rename; int skip_stat_unmatch; @@ -585,10 +585,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co int len, dtype; int exclude; - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) + if (is_dot_or_dotdot(de->d_name) || + !strcmp(de->d_name, ".git")) continue; len = strlen(de->d_name); /* Ignore overly long pathnames! */ @@ -779,6 +777,25 @@ int is_inside_dir(const char *dir) return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL; } +int is_empty_dir(const char *path) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 1; + + if (!dir) + return 0; + + while ((e = readdir(dir)) != NULL) + if (!is_dot_or_dotdot(e->d_name)) { + ret = 0; + break; + } + + closedir(dir); + return ret; +} + int remove_dir_recursively(struct strbuf *path, int only_empty) { DIR *dir = opendir(path->buf); @@ -793,10 +810,8 @@ int remove_dir_recursively(struct strbuf *path, int only_empty) len = path->len; while ((e = readdir(dir)) != NULL) { struct stat st; - if ((e->d_name[0] == '.') && - ((e->d_name[1] == 0) || - ((e->d_name[1] == '.') && e->d_name[2] == 0))) - continue; /* "." and ".." */ + if (is_dot_or_dotdot(e->d_name)) + continue; strbuf_setlen(path, len); strbuf_addstr(path, e->d_name); @@ -77,6 +77,15 @@ extern int file_exists(const char *); extern char *get_relative_cwd(char *buffer, int size, const char *dir); extern int is_inside_dir(const char *dir); +static inline int is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +extern int is_empty_dir(const char *dir); + extern void setup_standard_excludes(struct dir_struct *dir); extern int remove_dir_recursively(struct strbuf *path, int only_empty); @@ -1,5 +1,6 @@ #include "cache.h" #include "blob.h" +#include "dir.h" static void create_directories(const char *path, const struct checkout *state) { @@ -62,9 +63,7 @@ static void remove_subtree(const char *path) *name++ = '/'; while ((de = readdir(dir)) != NULL) { struct stat st; - if ((de->d_name[0] == '.') && - ((de->d_name[1] == 0) || - ((de->d_name[1] == '.') && de->d_name[2] == 0))) + if (is_dot_or_dotdot(de->d_name)) continue; strcpy(name, de->d_name); if (lstat(pathbuf, &st)) diff --git a/fast-import.c b/fast-import.c index a6bce66196..f0e08aca70 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1872,12 +1872,13 @@ static void file_change_m(struct branch *b) if (!p) die("Corrupt mode: %s", command_buf.buf); switch (mode) { + case 0644: + case 0755: + mode |= S_IFREG; case S_IFREG | 0644: case S_IFREG | 0755: case S_IFLNK: case S_IFGITLINK: - case 0644: - case 0755: /* ok */ break; default: @@ -1944,7 +1945,7 @@ static void file_change_m(struct branch *b) typename(type), command_buf.buf); } - tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL); + tree_content_set(&b->branch_tree, p, sha1, mode, NULL); } static void file_change_d(struct branch *b) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index b0223c3419..ca60356d00 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -800,6 +800,7 @@ y - stage this hunk n - do not stage this hunk a - stage this and all the remaining hunks in the file d - do not stage this hunk nor any of the remaining hunks in the file +g - select a hunk to go to j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk @@ -836,6 +837,47 @@ sub patch_update_cmd { } } +# Generate a one line summary of a hunk. +sub summarize_hunk { + my $rhunk = shift; + my $summary = $rhunk->{TEXT}[0]; + + # Keep the line numbers, discard extra context. + $summary =~ s/@@(.*?)@@.*/$1 /s; + $summary .= " " x (20 - length $summary); + + # Add some user context. + for my $line (@{$rhunk->{TEXT}}) { + if ($line =~ m/^[+-].*\w/) { + $summary .= $line; + last; + } + } + + chomp $summary; + return substr($summary, 0, 80) . "\n"; +} + + +# Print a one-line summary of each hunk in the array ref in +# the first argument, starting wih the index in the 2nd. +sub display_hunks { + my ($hunks, $i) = @_; + my $ctr = 0; + $i ||= 0; + for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) { + my $status = " "; + if (defined $hunks->[$i]{USE}) { + $status = $hunks->[$i]{USE} ? "+" : "-"; + } + printf "%s%2d: %s", + $status, + $i + 1, + summarize_hunk($hunks->[$i]); + } + return $i; +} + sub patch_update_file { my ($ix, $num); my $path = shift; @@ -904,6 +946,9 @@ sub patch_update_file { if ($ix < $num - 1) { $other .= '/J'; } + if ($num > 1) { + $other .= '/g'; + } for ($i = 0; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { $undecided = 1; @@ -937,6 +982,28 @@ sub patch_update_file { } next; } + elsif ($other =~ /g/ && $line =~ /^g(.*)/) { + my $response = $1; + my $no = $ix > 10 ? $ix - 10 : 0; + while ($response eq '') { + my $extra = ""; + $no = display_hunks(\@hunk, $no); + if ($no < $num) { + $extra = " (<ret> to see more)"; + } + print "go to which hunk$extra? "; + $response = <STDIN>; + chomp $response; + } + if ($response !~ /^\s*\d+\s*$/) { + print STDERR "Invalid number: '$response'\n"; + } elsif (0 < $response && $response <= $num) { + $ix = $response - 1; + } else { + print STDERR "Sorry, only $num hunks available.\n"; + } + next; + } elsif ($line =~ /^d/i) { while ($ix < $num) { if (!defined $hunk[$ix]{USE}) { diff --git a/git-bisect.sh b/git-bisect.sh index 17a35f6adc..85db4ba400 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -508,7 +508,7 @@ bisect_visualize() { if test $# = 0 then - case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in + case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in '') set git log ;; set*) set gitk ;; esac diff --git a/git-cvsserver.perl b/git-cvsserver.perl index b0a805c688..fef7faf339 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -1358,7 +1358,13 @@ sub req_ci # write our commit message out if we have one ... my ( $msg_fh, $msg_filename ) = tempfile( DIR => $TEMP_DIR ); print $msg_fh $state->{opt}{m};# if ( exists ( $state->{opt}{m} ) ); - print $msg_fh "\n\nvia git-CVS emulator\n"; + if ( defined ( $cfg->{gitcvs}{commitmsgannotation} ) ) { + if ($cfg->{gitcvs}{commitmsgannotation} !~ /^\s*$/ ) { + print $msg_fh "\n\n".$cfg->{gitcvs}{commitmsgannotation}."\n" + } + } else { + print $msg_fh "\n\nvia git-CVS emulator\n"; + } close $msg_fh; my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`; @@ -2527,12 +2533,18 @@ sub open_blob_or_die return $fh; } -# Generate a CVS author name from Git author information, by taking -# the first eight characters of the user part of the email address. +# Generate a CVS author name from Git author information, by taking the local +# part of the email address and replacing characters not in the Portable +# Filename Character Set (see IEEE Std 1003.1-2001, 3.276) by underscores. CVS +# Login names are Unix login names, which should be restricted to this +# character set. sub cvs_author { my $author_line = shift; - (my $author) = $author_line =~ /<([^>@]{1,8})/; + (my $author) = $author_line =~ /<([^@>]*)/; + + $author =~ s/[^-a-zA-Z0-9_.]/_/g; + $author =~ s/^-/_/; $author; } diff --git a/git-filter-branch.sh b/git-filter-branch.sh index c106f45af7..eb62f719b0 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -40,6 +40,16 @@ skip_commit() done; } +# if you run 'git_commit_non_empty_tree "$@"' in a commit filter, +# it will skip commits that leave the tree untouched, commit the other. +git_commit_non_empty_tree() +{ + if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then + map "$3" + else + git commit-tree "$@" + fi +} # override die(): this version puts in an extra line break, so that # the progress is still visible @@ -109,11 +119,12 @@ filter_tree= filter_index= filter_parent= filter_msg=cat -filter_commit='git commit-tree "$@"' +filter_commit= filter_tag_name= filter_subdir= orig_namespace=refs/original/ force= +prune_empty= while : do case "$1" in @@ -126,6 +137,11 @@ do force=t continue ;; + --prune-empty) + shift + prune_empty=t + continue + ;; -*) ;; *) @@ -176,6 +192,17 @@ do esac done +case "$prune_empty,$filter_commit" in +,) + filter_commit='git commit-tree "$@"';; +t,) + filter_commit="$functions;"' git_commit_non_empty_tree "$@"';; +,*) + ;; +*) + die "Cannot set --prune-empty and --filter-commit at the same time" +esac + case "$force" in t) rm -rf "$tempdir" diff --git a/git-mergetool.sh b/git-mergetool.sh index d4078a6aff..b2d53752ae 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -8,7 +8,7 @@ # at the discretion of Junio C Hamano. # -USAGE='[--tool=tool] [file to merge] ...' +USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= . git-sh-setup @@ -70,16 +70,16 @@ resolve_symlink_merge () { git checkout-index -f --stage=2 -- "$MERGED" git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [rR]*) git checkout-index -f --stage=3 -- "$MERGED" git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [aA]*) - exit 1 + return 1 ;; esac done @@ -97,15 +97,15 @@ resolve_deleted_merge () { [mMcC]*) git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [dD]*) git rm -- "$MERGED" > /dev/null cleanup_temp_files - return + return 0 ;; [aA]*) - exit 1 + return 1 ;; esac done @@ -137,7 +137,7 @@ merge_file () { else echo "$MERGED: file does not need merging" fi - exit 1 + return 1 fi ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" @@ -176,8 +176,10 @@ merge_file () { echo "Normal merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" - printf "Hit return to start merge resolution tool (%s): " "$merge_tool" - read ans + if "$prompt" = true; then + printf "Hit return to start merge resolution tool (%s): " "$merge_tool" + read ans + fi case "$merge_tool" in kdiff3) @@ -267,7 +269,12 @@ merge_file () { if test "$status" -ne 0; then echo "merge of $MERGED failed" 1>&2 mv -- "$BACKUP" "$MERGED" - exit 1 + + if test "$merge_keep_temporaries" = "false"; then + cleanup_temp_files + fi + + return 1 fi if test "$merge_keep_backup" = "true"; then @@ -278,8 +285,11 @@ merge_file () { git add -- "$MERGED" cleanup_temp_files + return 0 } +prompt=$(git config --bool mergetool.prompt || echo true) + while test $# != 0 do case "$1" in @@ -295,6 +305,12 @@ do shift ;; esac ;; + -y|--no-prompt) + prompt=false + ;; + --prompt) + prompt=true + ;; --) shift break @@ -341,6 +357,22 @@ init_merge_tool_path() { fi } +prompt_after_failed_merge() { + while true; do + printf "Continue merging other unresolved paths (y/n) ? " + read ans + case "$ans" in + + [yY]*) + return 0 + ;; + + [nN]*) + return 1 + ;; + esac + done +} if test -z "$merge_tool"; then merge_tool=`git config merge.tool` @@ -389,6 +421,7 @@ else init_merge_tool_path "$merge_tool" merge_keep_backup="$(git config --bool merge.keepBackup || echo true)" + merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then echo "The merge tool $merge_tool is not available as '$merge_tool_path'" @@ -400,27 +433,44 @@ else fi fi +last_status=0 +rollup_status=0 if test $# -eq 0 ; then - files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` - if test -z "$files" ; then - echo "No files need merging" - exit 0 + files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` + if test -z "$files" ; then + echo "No files need merging" + exit 0 + fi + echo Merging the files: "$files" + git ls-files -u | + sed -e 's/^[^ ]* //' | + sort -u | + while IFS= read i + do + if test $last_status -ne 0; then + prompt_after_failed_merge < /dev/tty || exit 1 fi - echo Merging the files: "$files" - git ls-files -u | - sed -e 's/^[^ ]* //' | - sort -u | - while IFS= read i - do - printf "\n" - merge_file "$i" < /dev/tty > /dev/tty - done + printf "\n" + merge_file "$i" < /dev/tty > /dev/tty + last_status=$? + if test $last_status -ne 0; then + rollup_status=1 + fi + done else - while test $# -gt 0; do - printf "\n" - merge_file "$1" - shift - done + while test $# -gt 0; do + if test $last_status -ne 0; then + prompt_after_failed_merge || exit 1 + fi + printf "\n" + merge_file "$1" + last_status=$? + if test $last_status -ne 0; then + rollup_status=1 + fi + shift + done fi -exit 0 + +exit $rollup_status diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c8b0861c08..21ac20c305 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -27,6 +27,7 @@ continue continue rebasing process abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation +root rebase all reachable commmits up to the root(s) " . git-sh-setup @@ -44,6 +45,7 @@ STRATEGY= ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= +REBASE_ROOT= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -154,6 +156,11 @@ pick_one () { output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return + if test ! -z "$REBASE_ROOT" + then + output git cherry-pick "$@" + return + fi parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) @@ -197,7 +204,11 @@ pick_one_preserving_merges () { # rewrite parents; if none were rewritten, we can fast-forward. new_parents= - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)" + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" + if test "$pend" = " " + then + pend=" root" + fi while [ "$pend" != "" ] do p=$(expr "$pend" : ' \([^ ]*\)') @@ -227,7 +238,9 @@ pick_one_preserving_merges () { if test -f "$DROPPED"/$p then fast_forward=f - pend=" $(cat "$DROPPED"/$p)$pend" + replacement="$(cat "$DROPPED"/$p)" + test -z "$replacement" && replacement=root + pend=" $replacement$pend" else new_parents="$new_parents $p" fi @@ -349,7 +362,7 @@ do_next () { squash|s) comment_for_reflog squash - has_action "$DONE" || + test -f "$DONE" && has_action "$DONE" || die "Cannot 'squash' without a previous commit" mark_action_done @@ -443,6 +456,7 @@ get_saved_options () { test -d "$REWRITTEN" && PRESERVE_MERGES=t test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" test -f "$DOTEST"/verbose && VERBOSE=t + test ! -s "$DOTEST"/upstream && REBASE_ROOT=t } while test $# != 0 @@ -547,6 +561,9 @@ first and then run 'git rebase --continue' again." -i) # yeah, we know ;; + --root) + REBASE_ROOT=t + ;; --onto) shift ONTO=$(git rev-parse --verify "$1") || @@ -554,27 +571,36 @@ first and then run 'git rebase --continue' again." ;; --) shift - run_pre_rebase_hook ${1+"$@"} - test $# -eq 1 -o $# -eq 2 || usage + test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage test -d "$DOTEST" && die "Interactive rebase already started" git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first" + if test -z "$REBASE_ROOT" + then + UPSTREAM_ARG="$1" + UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + test -z "$ONTO" && ONTO=$UPSTREAM + shift + else + UPSTREAM_ARG=--root + test -z "$ONTO" && + die "You must specify --onto when using --root" + fi + run_pre_rebase_hook "$UPSTREAM_ARG" "$@" + comment_for_reflog start require_clean_work_tree - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" - test -z "$ONTO" && ONTO=$UPSTREAM - - if test ! -z "$2" + if test ! -z "$1" then - output git show-ref --verify --quiet "refs/heads/$2" || - die "Invalid branchname: $2" - output git checkout "$2" || - die "Could not checkout $2" + output git show-ref --verify --quiet "refs/heads/$1" || + die "Invalid branchname: $1" + output git checkout "$1" || + die "Could not checkout $1" fi HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" @@ -598,12 +624,19 @@ first and then run 'git rebase --continue' again." # This ensures that commits on merged, but otherwise # unrelated side branches are left alone. (Think "X" # in the man page's example.) - mkdir "$REWRITTEN" && - for c in $(git merge-base --all $HEAD $UPSTREAM) - do - echo $ONTO > "$REWRITTEN"/$c || + if test -z "$REBASE_ROOT" + then + mkdir "$REWRITTEN" && + for c in $(git merge-base --all $HEAD $UPSTREAM) + do + echo $ONTO > "$REWRITTEN"/$c || + die "Could not init rewritten commits" + done + else + mkdir "$REWRITTEN" && + echo $ONTO > "$REWRITTEN"/root || die "Could not init rewritten commits" - done + fi # No cherry-pick because our first pass is to determine # parents to rewrite and skipping dropped commits would # prematurely end our probe @@ -613,12 +646,21 @@ first and then run 'git rebase --continue' again." MERGES_OPTION="--no-merges --cherry-pick" fi - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) SHORTHEAD=$(git rev-parse --short $HEAD) SHORTONTO=$(git rev-parse --short $ONTO) + if test -z "$REBASE_ROOT" + # this is now equivalent to ! -z "$UPSTREAM" + then + SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) + REVISIONS=$UPSTREAM...$HEAD + SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD + else + REVISIONS=$ONTO...$HEAD + SHORTREVISIONS=$SHORTHEAD + fi git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ --abbrev=7 --reverse --left-right --topo-order \ - $UPSTREAM...$HEAD | \ + $REVISIONS | \ sed -n "s/^>//p" | while read shortsha1 rest do if test t != "$PRESERVE_MERGES" @@ -626,14 +668,19 @@ first and then run 'git rebase --continue' again." echo "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) - do - if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) - then - preserve=f - fi - done + if test -z "$REBASE_ROOT" + then + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) + do + if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) + then + preserve=f + fi + done + else + preserve=f + fi if test f = "$preserve" then touch "$REWRITTEN"/$sha1 @@ -647,11 +694,11 @@ first and then run 'git rebase --continue' again." then mkdir "$DROPPED" # Save all non-cherry-picked changes - git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \ + git rev-list $REVISIONS --left-right --cherry-pick | \ sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks # Now all commits and note which ones are missing in # not-cherry-picks and hence being dropped - git rev-list $UPSTREAM..$HEAD | + git rev-list $REVISIONS | while read rev do if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = "" @@ -660,17 +707,18 @@ first and then run 'git rebase --continue' again." # not worthwhile, we don't want to track its multiple heads, # just the history of its first-parent for others that will # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" rm "$REWRITTEN"/$rev fi done fi + test -s "$TODO" || echo noop >> "$TODO" cat >> "$TODO" << EOF -# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO +# Rebase $SHORTREVISIONS onto $SHORTONTO # # Commands: # p, pick = use commit diff --git a/git-rebase.sh b/git-rebase.sh index ebd4df3a0e..6d3eddbada 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]' +USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge prec=4 verbose= git_am_opt= +rebase_root= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -297,6 +298,9 @@ do -C*) git_am_opt="$git_am_opt $1" ;; + --root) + rebase_root=t + ;; -*) usage ;; @@ -344,17 +348,29 @@ case "$diff" in ;; esac -# The upstream head must be given. Make sure it is valid. -upstream_name="$1" -upstream=`git rev-parse --verify "${upstream_name}^0"` || - die "invalid upstream $upstream_name" +if test -z "$rebase_root" +then + # The upstream head must be given. Make sure it is valid. + upstream_name="$1" + shift + upstream=`git rev-parse --verify "${upstream_name}^0"` || + die "invalid upstream $upstream_name" + unset root_flag + upstream_arg="$upstream_name" +else + test -z "$newbase" && die "--root must be used with --onto" + unset upstream_name + unset upstream + root_flag="--root" + upstream_arg="$root_flag" +fi # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} onto=$(git rev-parse --verify "${onto_name}^0") || exit # If a hook exists, give it a chance to interrupt -run_pre_rebase_hook ${1+"$@"} +run_pre_rebase_hook "$upstream_arg" "$@" # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) @@ -362,16 +378,16 @@ run_pre_rebase_hook ${1+"$@"} # $head_name -- refs/heads/<that-branch> or "detached HEAD" switch_to= case "$#" in -2) +1) # Is it "rebase other $branchname" or "rebase other $commit"? - branch_name="$2" - switch_to="$2" + branch_name="$1" + switch_to="$1" - if git show-ref --verify --quiet -- "refs/heads/$2" && - branch=$(git rev-parse -q --verify "refs/heads/$2") + if git show-ref --verify --quiet -- "refs/heads/$1" && + branch=$(git rev-parse -q --verify "refs/heads/$1") then - head_name="refs/heads/$2" - elif branch=$(git rev-parse -q --verify "$2") + head_name="refs/heads/$1" + elif branch=$(git rev-parse -q --verify "$1") then head_name="detached HEAD" else @@ -393,7 +409,8 @@ case "$#" in esac orig_head=$branch -# Now we are rebasing commits $upstream..$branch on top of $onto +# Now we are rebasing commits $upstream..$branch (or with --root, +# everything leading up to $branch) on top of $onto # Check if we are already based on $onto with linear history, # but this should be done only when upstream and onto are the same. @@ -429,10 +446,17 @@ then exit 0 fi +if test -n "$rebase_root" +then + revisions="$onto..$orig_head" +else + revisions="$upstream..$orig_head" +fi + if test -z "$do_merge" then git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - "$upstream..$orig_head" | + $root_flag "$revisions" | git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? @@ -455,7 +479,7 @@ echo "$orig_head" > "$dotest/orig-head" echo "$head_name" > "$dotest/head-name" msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"` +for cmt in `git rev-list --reverse --no-merges "$revisions"` do msgnum=$(($msgnum + 1)) echo "$cmt" > "$dotest/cmt.$msgnum" diff --git a/git-sh-setup.sh b/git-sh-setup.sh index f07d96b9b5..2142308bcc 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -96,7 +96,7 @@ cd_to_toplevel () { ..|../*|*/..|*/../*) # Interpret $cdup relative to the physical, not logical, cwd. # Probably /bin/pwd is more portable than passing -P to cd or pwd. - phys="$(/bin/pwd)/$cdup" + phys="$(unset PWD; /bin/pwd)/$cdup" ;; *) # There's no "..", so no need to make things absolute. @@ -158,7 +158,7 @@ static int handle_alias(int *argcp, const char ***argv) if (ret >= 0 && WIFEXITED(ret) && WEXITSTATUS(ret) != 127) exit(WEXITSTATUS(ret)); - die("Failed to run '%s' when expanding alias '%s'\n", + die("Failed to run '%s' when expanding alias '%s'", alias_string + 1, alias_command); } count = split_cmdline(alias_string, &new_argv); @@ -416,21 +416,42 @@ static void execv_dashed_external(const char **argv) strbuf_release(&cmd); } +static int run_argv(int *argcp, const char ***argv) +{ + int done_alias = 0; + + while (1) { + /* See if it's an internal command */ + handle_internal_command(*argcp, *argv); + + /* .. then try the external ones */ + execv_dashed_external(*argv); + + /* It could be an alias -- this works around the insanity + * of overriding "git log" with "git show" by having + * alias.log = show + */ + if (done_alias || !handle_alias(argcp, argv)) + break; + done_alias = 1; + } + + return done_alias; +} + int main(int argc, const char **argv) { const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help"; char *slash = (char *)cmd + strlen(cmd); - int done_alias = 0; /* * Take the basename of argv[0] as the command * name, and the dirname as the default exec_path * if we don't have anything better. */ - do - --slash; - while (cmd <= slash && !is_dir_sep(*slash)); + while (cmd <= slash && !is_dir_sep(*slash)) + slash--; if (cmd <= slash) { *slash++ = 0; git_set_argv0_path(cmd); @@ -480,31 +501,22 @@ int main(int argc, const char **argv) setup_path(); while (1) { - /* See if it's an internal command */ - handle_internal_command(argc, argv); - - /* .. then try the external ones */ - execv_dashed_external(argv); - - /* It could be an alias -- this works around the insanity - * of overriding "git log" with "git show" by having - * alias.log = show - */ - if (done_alias || !handle_alias(&argc, &argv)) + static int done_help = 0; + static int was_alias = 0; + was_alias = run_argv(&argc, &argv); + if (errno != ENOENT) break; - done_alias = 1; - } - - if (errno == ENOENT) { - if (done_alias) { + if (was_alias) { fprintf(stderr, "Expansion of alias '%s' failed; " "'%s' is not a git-command\n", cmd, argv[0]); exit(1); } - argv[0] = help_unknown_cmd(cmd); - handle_internal_command(argc, argv); - execv_dashed_external(argv); + if (!done_help) { + cmd = argv[0] = help_unknown_cmd(cmd); + done_help = 1; + } else + break; } fprintf(stderr, "Failed to run command '%s': %s\n", diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 99f71b47c2..9a5cfb0cb1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -203,7 +203,7 @@ our %feature = ( # $feature{'blame'}{'override'} = 1; # and in project config gitweb.blame = 0|1; 'blame' => { - 'sub' => \&feature_blame, + 'sub' => sub { feature_bool('blame', @_) }, 'override' => 0, 'default' => [0]}, @@ -241,7 +241,7 @@ our %feature = ( # $feature{'grep'}{'override'} = 1; # and in project config gitweb.grep = 0|1; 'grep' => { - 'sub' => \&feature_grep, + 'sub' => sub { feature_bool('grep', @_) }, 'override' => 0, 'default' => [1]}, @@ -255,7 +255,7 @@ our %feature = ( # $feature{'pickaxe'}{'override'} = 1; # and in project config gitweb.pickaxe = 0|1; 'pickaxe' => { - 'sub' => \&feature_pickaxe, + 'sub' => sub { feature_bool('pickaxe', @_) }, 'override' => 0, 'default' => [1]}, @@ -330,6 +330,21 @@ our %feature = ( 'ctags' => { 'override' => 0, 'default' => [0]}, + + # The maximum number of patches in a patchset generated in patch + # view. Set this to 0 or undef to disable patch view, or to a + # negative number to remove any limit. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'patches'}{'default'} = [0]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'patches'}{'override'} = 1; + # and in project config gitweb.patches = 0|n; + # where n is the maximum number of patches allowed in a patchset. + 'patches' => { + 'sub' => \&feature_patches, + 'override' => 0, + 'default' => [16]}, ); sub gitweb_get_feature { @@ -363,16 +378,17 @@ sub gitweb_check_feature { } -sub feature_blame { - my ($val) = git_get_project_config('blame', '--bool'); +sub feature_bool { + my $key = shift; + my ($val) = git_get_project_config($key, '--bool'); if ($val eq 'true') { - return 1; + return (1); } elsif ($val eq 'false') { - return 0; + return (0); } - return $_[0]; + return ($_[0]); } sub feature_snapshot { @@ -387,25 +403,11 @@ sub feature_snapshot { return @fmts; } -sub feature_grep { - my ($val) = git_get_project_config('grep', '--bool'); +sub feature_patches { + my @val = (git_get_project_config('patches', '--int')); - if ($val eq 'true') { - return (1); - } elsif ($val eq 'false') { - return (0); - } - - return ($_[0]); -} - -sub feature_pickaxe { - my ($val) = git_get_project_config('pickaxe', '--bool'); - - if ($val eq 'true') { - return (1); - } elsif ($val eq 'false') { - return (0); + if (@val) { + return @val; } return ($_[0]); @@ -504,6 +506,8 @@ our %actions = ( "heads" => \&git_heads, "history" => \&git_history, "log" => \&git_log, + "patch" => \&git_patch, + "patches" => \&git_patches, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@ -4576,28 +4580,33 @@ sub git_tag { } sub git_blame { - my $fd; - my $ftype; - + # permissions gitweb_check_feature('blame') - or die_error(403, "Blame view not allowed"); + or die_error(403, "Blame view not allowed"); + # error checking die_error(400, "No file name given") unless $file_name; $hash_base ||= git_get_head_hash($project); - die_error(404, "Couldn't find base commit") unless ($hash_base); + die_error(404, "Couldn't find base commit") unless $hash_base; my %co = parse_commit($hash_base) or die_error(404, "Commit not found"); + my $ftype = "blob"; if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") or die_error(404, "Error looking up file"); + } else { + $ftype = git_get_type($hash); + if ($ftype !~ "blob") { + die_error(400, "Object is not a blob"); + } } - $ftype = git_get_type($hash); - if ($ftype !~ "blob") { - die_error(400, "Object is not a blob"); - } - open ($fd, "-|", git_cmd(), "blame", '-p', '--', - $file_name, $hash_base) + + # run git-blame --porcelain + open my $fd, "-|", git_cmd(), "blame", '-p', + $hash_base, '--', $file_name or die_error(500, "Open git-blame failed"); + + # page header git_header_html(); my $formats_nav = $cgi->a({-href => href(action=>"blob", -replay=>1)}, @@ -4611,42 +4620,46 @@ sub git_blame { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); - my @rev_color = (qw(light2 dark2)); + + # page body + my @rev_color = qw(light2 dark2); my $num_colors = scalar(@rev_color); my $current_color = 0; - my $last_rev; + my %metainfo = (); + print <<HTML; <div class="page_body"> <table class="blame"> <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML - my %metainfo = (); - while (1) { - $_ = <$fd>; - last unless defined $_; + LINE: + while (my $line = <$fd>) { + chomp $line; + # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>] + # no <lines in group> for subsequent lines in group of lines my ($full_rev, $orig_lineno, $lineno, $group_size) = - /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = {}; } my $meta = $metainfo{$full_rev}; - while (<$fd>) { - last if (s/^\t//); - if (/^(\S+) (.*)$/) { + my $data; + while ($data = <$fd>) { + chomp $data; + last if ($data =~ s/^\t//); # contents of line + if ($data =~ /^(\S+) (.*)$/) { $meta->{$1} = $2; } } - my $data = $_; - chomp $data; - my $rev = substr($full_rev, 0, 8); + my $short_rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; - my %date = parse_date($meta->{'author-time'}, - $meta->{'author-tz'}); + my %date = + parse_date($meta->{'author-time'}, $meta->{'author-tz'}); my $date = $date{'iso-tz'}; if ($group_size) { - $current_color = ++$current_color % $num_colors; + $current_color = ($current_color + 1) % $num_colors; } - print "<tr class=\"$rev_color[$current_color]\">\n"; + print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n"; if ($group_size) { print "<td class=\"sha1\""; print " title=\"". esc_html($author) . ", $date\""; @@ -4655,20 +4668,25 @@ HTML print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)); + esc_html($short_rev)); print "</td>\n"; } - open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") - or die_error(500, "Open git-rev-parse failed"); - my $parent_commit = <$dd>; - close $dd; - chomp($parent_commit); + my $parent_commit; + if (!exists $meta->{'parent'}) { + open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") + or die_error(500, "Open git-rev-parse failed"); + $parent_commit = <$dd>; + close $dd; + chomp($parent_commit); + $meta->{'parent'} = $parent_commit; + } else { + $parent_commit = $meta->{'parent'}; + } my $blamed = href(action => 'blame', file_name => $meta->{'filename'}, hash_base => $parent_commit); print "<td class=\"linenr\">"; print $cgi->a({ -href => "$blamed#l$orig_lineno", - -id => "l$lineno", -class => "linenr" }, esc_html($lineno)); print "</td>"; @@ -4679,6 +4697,8 @@ HTML print "</div>"; close $fd or print "Reading blob failed\n"; + + # page footer git_footer_html(); } @@ -4998,6 +5018,15 @@ sub git_log { my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100); + my ($patch_max) = gitweb_get_feature('patches'); + if ($patch_max) { + if ($patch_max < 0 || @commitlist <= $patch_max) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"patches", -replay=>1)}, + "patches"); + } + } + git_header_html(); git_print_page_nav('log','', $hash,undef,undef, $paging_nav); @@ -5077,6 +5106,11 @@ sub git_commit { } @$parents ) . ')'; } + if (gitweb_check_feature('patches')) { + $formats_nav .= " | " . + $cgi->a({-href => href(action=>"patch", -replay=>1)}, + "patch"); + } if (!defined $parent) { $parent = "--root"; @@ -5353,7 +5387,14 @@ sub git_blobdiff_plain { } sub git_commitdiff { - my $format = shift || 'html'; + my %params = @_; + my $format = $params{-format} || 'html'; + + my ($patch_max) = gitweb_get_feature('patches'); + if ($format eq 'patch') { + die_error(403, "Patch view not allowed") unless $patch_max; + } + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash) or die_error(404, "Unknown commit object"); @@ -5368,6 +5409,11 @@ sub git_commitdiff { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); + if ($patch_max) { + $formats_nav .= " | " . + $cgi->a({-href => href(action=>"patch", -replay=>1)}, + "patch"); + } if (defined $hash_parent && $hash_parent ne '-c' && $hash_parent ne '--cc') { @@ -5451,7 +5497,31 @@ sub git_commitdiff { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', $hash_parent_param, $hash, "--" or die_error(500, "Open git-diff-tree failed"); - + } elsif ($format eq 'patch') { + # For commit ranges, we limit the output to the number of + # patches specified in the 'patches' feature. + # For single commits, we limit the output to a single patch, + # diverging from the git-format-patch default. + my @commit_spec = (); + if ($hash_parent) { + if ($patch_max > 0) { + push @commit_spec, "-$patch_max"; + } + push @commit_spec, '-n', "$hash_parent..$hash"; + } else { + if ($params{-single}) { + push @commit_spec, '-1'; + } else { + if ($patch_max > 0) { + push @commit_spec, "-$patch_max"; + } + push @commit_spec, "-n"; + } + push @commit_spec, '--root', $hash; + } + open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8', + '--stdout', @commit_spec + or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); } @@ -5500,6 +5570,14 @@ sub git_commitdiff { print to_utf8($line) . "\n"; } print "---\n\n"; + } elsif ($format eq 'patch') { + my $filename = basename($project) . "-$hash.patch"; + + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => 'inline; filename="' . "$filename" . '"'); } # write patch @@ -5521,11 +5599,25 @@ sub git_commitdiff { print <$fd>; close $fd or print "Reading git-diff-tree failed\n"; + } elsif ($format eq 'patch') { + local $/ = undef; + print <$fd>; + close $fd + or print "Reading git-format-patch failed\n"; } } sub git_commitdiff_plain { - git_commitdiff('plain'); + git_commitdiff(-format => 'plain'); +} + +# format-patch-style patches +sub git_patch { + git_commitdiff(-format => 'patch', -single=> 1); +} + +sub git_patches { + git_commitdiff(-format => 'patch'); } sub git_history { @@ -5878,6 +5970,14 @@ sub git_shortlog { $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } + my $patch_max = gitweb_check_feature('patches'); + if ($patch_max) { + if ($patch_max < 0 || @commitlist <= $patch_max) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"patches", -replay=>1)}, + "patches"); + } + } git_header_html(); git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); @@ -6146,8 +6246,8 @@ XML } my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; + my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1); + my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1); print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; } print <<XML; @@ -28,9 +28,31 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, p->next = NULL; } +static int isregexspecial(int c) +{ + return isspecial(c) || c == '$' || c == '(' || c == ')' || c == '+' || + c == '.' || c == '^' || c == '{' || c == '|'; +} + +static int is_fixed(const char *s) +{ + while (!isregexspecial(*s)) + s++; + return !*s; +} + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { - int err = regcomp(&p->regexp, p->pattern, opt->regflags); + int err; + + if (opt->fixed || is_fixed(p->pattern)) + p->fixed = 1; + if (opt->regflags & REG_ICASE) + p->fixed = 0; + if (p->fixed) + return; + + err = regcomp(&p->regexp, p->pattern, opt->regflags); if (err) { char errbuf[1024]; char where[1024]; @@ -159,8 +181,7 @@ void compile_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - if (!opt->fixed) - compile_regexp(p, opt); + compile_regexp(p, opt); break; default: opt->extended = 1; @@ -294,7 +315,6 @@ static struct { static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) { int hit = 0; - int at_true_bol = 1; int saved_ch = 0; regmatch_t pmatch[10]; @@ -315,7 +335,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol } again: - if (!opt->fixed) { + if (!p->fixed) { regex_t *exp = &p->regexp; hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), pmatch, 0); @@ -337,7 +357,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol * either end of the line, or at word boundary * (i.e. the next char must not be a word char). */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + if ( ((pmatch[0].rm_so == 0) || !word_char(bol[pmatch[0].rm_so-1])) && ((pmatch[0].rm_eo == (eol-bol)) || !word_char(bol[pmatch[0].rm_eo])) ) @@ -349,10 +369,14 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol /* There could be more than one match on the * line, and the first match might not be * strict word match. But later ones could be! + * Forward to the next possible start, i.e. the + * next position following a non-word char. */ bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; + while (word_char(bol[-1]) && bol < eol) + bol++; + if (bol < eol) + goto again; } } if (p->token == GREP_PATTERN_HEAD && saved_ch) @@ -395,7 +419,7 @@ static int match_expr_eval(struct grep_opt *o, h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1); break; default: - die("Unexpected node type (internal error) %d\n", x->node); + die("Unexpected node type (internal error) %d", x->node); } if (collect_hits) x->hit |= h; @@ -30,6 +30,7 @@ struct grep_pat { const char *pattern; enum grep_header_field field; regex_t regexp; + unsigned fixed:1; }; enum grep_expr_node { diff --git a/http-push.c b/http-push.c index 7c6460919b..6ad853e2d0 100644 --- a/http-push.c +++ b/http-push.c @@ -87,6 +87,7 @@ static struct object_list *objects; struct repo { char *url; + char *path; int path_len; int has_info_refs; int can_update_info_refs; @@ -1200,7 +1201,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout) /* Make sure leading directories exist for the remote ref */ ep = strchr(url + strlen(remote->url) + 1, '/'); while (ep) { - *ep = 0; + char saved_character = ep[1]; + ep[1] = '\0'; slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); @@ -1222,7 +1224,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) free(url); return NULL; } - *ep = '/'; + ep[1] = saved_character; ep = strchr(ep + 1, '/'); } @@ -1424,9 +1426,17 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->userFunc(ls); } } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { - ls->dentry_name = xmalloc(strlen(ctx->cdata) - - remote->path_len + 1); - strcpy(ls->dentry_name, ctx->cdata + remote->path_len); + char *path = ctx->cdata; + if (*ctx->cdata == 'h') { + path = strstr(path, "//"); + if (path) { + path = strchr(path+2, '/'); + } + } + if (path) { + path += remote->path_len; + ls->dentry_name = xstrdup(path); + } } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { ls->dentry_flags |= IS_DIR; } @@ -1437,6 +1447,12 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) } } +/* + * NEEDSWORK: remote_ls() ignores info/refs on the remote side. But it + * should _only_ heed the information from that file, instead of trying to + * determine the refs from the remote file system (badly: it does not even + * know about packed-refs). + */ static void remote_ls(const char *path, int flags, void (*userFunc)(struct remote_ls_ctx *ls), void *userData) @@ -2206,10 +2222,11 @@ int main(int argc, char **argv) if (!remote->url) { char *path = strstr(arg, "//"); remote->url = arg; + remote->path_len = strlen(arg); if (path) { - path = strchr(path+2, '/'); - if (path) - remote->path_len = strlen(path); + remote->path = strchr(path+2, '/'); + if (remote->path) + remote->path_len = strlen(remote->path); } continue; } @@ -2238,8 +2255,9 @@ int main(int argc, char **argv) rewritten_url = xmalloc(strlen(remote->url)+2); strcpy(rewritten_url, remote->url); strcat(rewritten_url, "/"); + remote->path = rewritten_url + (remote->path - remote->url); + remote->path_len++; remote->url = rewritten_url; - ++remote->path_len; } /* Verify DAV compliance/lock support */ diff --git a/imap-send.c b/imap-send.c index 3703dbd1af..c3fa0df855 100644 --- a/imap-send.c +++ b/imap-send.c @@ -115,9 +115,9 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) len = vsnprintf(tmp, sizeof(tmp), fmt, ap); if (len < 0) - die("Fatal: Out of memory\n"); + die("Fatal: Out of memory"); if (len >= sizeof(tmp)) - die("imap command overflow !\n"); + die("imap command overflow!"); *strp = xmemdupz(tmp, len); return len; } @@ -482,7 +482,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...) va_start(va, fmt); if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen) - die("Fatal: buffer too small. Please report a bug.\n"); + die("Fatal: buffer too small. Please report a bug."); va_end(va); return ret; } diff --git a/index-pack.c b/index-pack.c index 60ed41a993..2931511e8c 100644 --- a/index-pack.c +++ b/index-pack.c @@ -178,7 +178,7 @@ static char *open_pack_file(char *pack_name) } else output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); if (output_fd < 0) - die("unable to create %s: %s\n", pack_name, strerror(errno)); + die("unable to create %s: %s", pack_name, strerror(errno)); pack_fd = output_fd; } else { input_fd = open(pack_name, O_RDONLY); diff --git a/merge-recursive.c b/merge-recursive.c index a0c804c817..b97026bd5c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -447,6 +447,30 @@ static void flush_buffer(int fd, const char *buf, unsigned long size) } } +static int would_lose_untracked(const char *path) +{ + int pos = cache_name_pos(path, strlen(path)); + + if (pos < 0) + pos = -1 - pos; + while (pos < active_nr && + !strcmp(path, active_cache[pos]->name)) { + /* + * If stage #0, it is definitely tracked. + * If it has stage #2 then it was tracked + * before this merge started. All other + * cases the path was not tracked. + */ + switch (ce_stage(active_cache[pos])) { + case 0: + case 2: + return 0; + } + pos++; + } + return file_exists(path); +} + static int make_room_for_path(const char *path) { int status; @@ -462,6 +486,14 @@ static int make_room_for_path(const char *path) die(msg, path, ""); } + /* + * Do not unlink a file in the work tree if we are not + * tracking it. + */ + if (would_lose_untracked(path)) + return error("refusing to lose untracked file at '%s'", + path); + /* Successful unlink is good.. */ if (!unlink(path)) return 0; @@ -902,6 +934,11 @@ static int process_renames(struct merge_options *o, ren1_src, ren1_dst, branch1, branch2); update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + update_stages(ren1_dst, NULL, + branch1 == o->branch1 ? + ren1->pair->two : NULL, + branch1 == o->branch1 ? + NULL : ren1->pair->two, 1); } else if (!sha_eq(dst_other.sha1, null_sha1)) { const char *new_path; clean_merge = 0; diff --git a/pack-redundant.c b/pack-redundant.c index 25b81a445c..e93eb966e2 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -463,7 +463,7 @@ static void minimize(struct pack_list **min) pll_free(perm_all); } if (perm_ok == NULL) - die("Internal error: No complete sets found!\n"); + die("Internal error: No complete sets found!"); /* find the permutation with the smallest size */ perm = perm_ok; @@ -573,14 +573,14 @@ static struct pack_list * add_pack_file(char *filename) struct packed_git *p = packed_git; if (strlen(filename) < 40) - die("Bad pack filename: %s\n", filename); + die("Bad pack filename: %s", filename); while (p) { if (strstr(p->pack_name, filename)) return add_pack(p); p = p->next; } - die("Filename %s not found in packed_git\n", filename); + die("Filename %s not found in packed_git", filename); } static void load_all(void) @@ -636,7 +636,7 @@ int main(int argc, char **argv) add_pack_file(*(argv + i++)); if (local_packs == NULL) - die("Zero packs found!\n"); + die("Zero packs found!"); load_all_objects(); diff --git a/perl/Git.pm b/perl/Git.pm index 8392a68333..7d7f2b1d36 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -166,11 +166,12 @@ sub repository { } } - if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) { - $opts{Directory} ||= '.'; + if (not defined $opts{Repository} and not defined $opts{WorkingCopy} + and not defined $opts{Directory}) { + $opts{Directory} = '.'; } - if ($opts{Directory}) { + if (defined $opts{Directory}) { -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); my $search = Git->repository(WorkingCopy => $opts{Directory}); @@ -1010,8 +1011,8 @@ sub _temp_cache { my $temp_fd = \$TEMP_FILEMAP{$name}; if (defined $$temp_fd and $$temp_fd->opened) { if ($TEMP_FILES{$$temp_fd}{locked}) { - throw Error::Simple("Temp file with moniker '", - $name, "' already in use"); + throw Error::Simple("Temp file with moniker '" . + $name . "' already in use"); } } else { if (defined $$temp_fd) { @@ -486,8 +486,8 @@ static void parse_commit_header(struct format_commit_context *context) context->commit_header_parsed = 1; } -static const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator) +const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator) { int first = 1; @@ -4,6 +4,7 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "dir.h" static struct refspec s_tag_refspec = { 0, @@ -634,10 +635,7 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) static int valid_remote_nick(const char *name) { - if (!name[0] || /* not empty */ - (name[0] == '.' && /* not "." */ - (!name[1] || /* not ".." */ - (name[1] == '.' && !name[2])))) + if (!name[0] || is_dot_or_dotdot(name)) return 0; return !strchr(name, '/'); /* no slash */ } diff --git a/sha1_file.c b/sha1_file.c index 52d1ead15b..f08493f039 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1700,6 +1700,9 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, delta_base_cache_lru.prev = &ent->lru; } +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size); + static void *unpack_delta_entry(struct packed_git *p, struct pack_window **w_curs, off_t curpos, @@ -2130,8 +2133,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, return 0; } -void *read_object(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -256,18 +256,21 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) { size_t res; + size_t oldalloc = sb->alloc; strbuf_grow(sb, size); res = fread(sb->buf + sb->len, 1, size, f); - if (res > 0) { + if (res > 0) strbuf_setlen(sb, sb->len + res); - } + else if (res < 0 && oldalloc == 0) + strbuf_release(sb); return res; } ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) { size_t oldlen = sb->len; + size_t oldalloc = sb->alloc; strbuf_grow(sb, hint ? hint : 8192); for (;;) { @@ -275,7 +278,10 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1); if (cnt < 0) { - strbuf_setlen(sb, oldlen); + if (oldalloc == 0) + strbuf_release(sb); + else + strbuf_setlen(sb, oldlen); return -1; } if (!cnt) @@ -292,6 +298,8 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) { + size_t oldalloc = sb->alloc; + if (hint < 32) hint = 32; @@ -311,7 +319,8 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) /* .. the buffer was too small - try again */ hint *= 2; } - strbuf_release(sb); + if (oldalloc == 0) + strbuf_release(sb); return -1; } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 4717c2d33b..fdb19a50f1 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -1,6 +1,8 @@ ServerName dummy PidFile httpd.pid DocumentRoot www +LogFormat "%h %l %u %t \"%r\" %>s %b" common +CustomLog access.log common ErrorLog error.log <IfDefine SSL> diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 4b44e131b2..271bc4e17f 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -341,4 +341,55 @@ test_expect_success \ check_cache_at DF/DF dirty && :' +test_expect_success \ + 'a/b (untracked) vs a case setup.' \ + 'rm -f .git/index && + : >a && + git update-index --add a && + treeM=`git write-tree` && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + git update-index --remove a && + mkdir a && + : >a/b && + treeH=`git write-tree` && + echo treeH $treeH && + git ls-tree $treeH' + +test_expect_success \ + 'a/b (untracked) vs a, plus c/d case test.' \ + '! git read-tree -u -m "$treeH" "$treeM" && + git ls-files --stage && + test -f a/b' + +test_expect_success \ + 'a/b vs a, plus c/d case setup.' \ + 'rm -f .git/index && + rm -fr a && + : >a && + mkdir c && + : >c/d && + git update-index --add a c/d && + treeM=`git write-tree` && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + mkdir a + : >a/b && + git update-index --add --remove a a/b && + treeH=`git write-tree` && + echo treeH $treeH && + git ls-tree $treeH' + +test_expect_success \ + 'a/b vs a, plus c/d case test.' \ + 'git read-tree -u -m "$treeH" "$treeM" && + git ls-files --stage | tee >treeMcheck.out && + test_cmp treeM.out treeMcheck.out' + test_done diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index 764bb0a6bc..15ebdc26eb 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -10,6 +10,10 @@ test_expect_success 'setup' ' git commit -m initial ' +test_expect_success 'checkout should not start branch from a tree' ' + test_must_fail git checkout -b newbranch master^{tree} +' + test_expect_success 'checkout master from invalid HEAD' ' echo 0000000000000000000000000000000000000000 >.git/HEAD && git checkout master -- diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh index beddb4e9f2..e42cbfe6c6 100755 --- a/t/t2300-cd-to-toplevel.sh +++ b/t/t2300-cd-to-toplevel.sh @@ -10,12 +10,12 @@ test_cd_to_toplevel () { cd '"'$1'"' && . git-sh-setup && cd_to_toplevel && - [ "$(/bin/pwd)" = "$TOPLEVEL" ] + [ "$(unset PWD; /bin/pwd)" = "$TOPLEVEL" ] ) ' } -TOPLEVEL="$(/bin/pwd)/repo" +TOPLEVEL="$(unset PWD; /bin/pwd)/repo" mkdir -p repo/sub/dir mv .git repo/ SUBDIRECTORY_OK=1 diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 7d10a27f1d..2cc8e7abe1 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -373,6 +373,38 @@ test_expect_success '--continue tries to commit, even for "edit"' ' test $parent = $(git rev-parse HEAD^) ' +test_expect_success 'aborted --continue does not squash commits after "edit"' ' + old=$(git rev-parse HEAD) && + test_tick && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo "edited again" > file7 && + git add file7 && + ( + FAKE_COMMIT_MESSAGE=" " && + export FAKE_COMMIT_MESSAGE && + test_must_fail git rebase --continue + ) && + test $old = $(git rev-parse HEAD) && + git rebase --abort +' + +test_expect_success 'auto-amend only edited commits after "edit"' ' + test_tick && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo "edited again" > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="edited file7 again" git commit && + echo "and again" > file7 && + git add file7 && + test_tick && + ( + FAKE_COMMIT_MESSAGE="and again" && + export FAKE_COMMIT_MESSAGE && + test_must_fail git rebase --continue + ) && + git rebase --abort +' + test_expect_success 'rebase a detached HEAD' ' grandparent=$(git rev-parse HEAD~2) && git checkout $(git rev-parse HEAD) && diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index aacfaae843..aacfaae843 100644..100755 --- a/t/t3411-rebase-preserve-around-merges.sh +++ b/t/t3411-rebase-preserve-around-merges.sh diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh new file mode 100755 index 0000000000..6359580262 --- /dev/null +++ b/t/t3412-rebase-root.sh @@ -0,0 +1,187 @@ +#!/bin/sh + +test_description='git rebase --root + +Tests if git rebase --root --onto <newparent> can rebase the root commit. +' +. ./test-lib.sh + +test_expect_success 'prepare repository' ' + echo 1 > A && + git add A && + git commit -m 1 && + echo 2 > A && + git add A && + git commit -m 2 && + git symbolic-ref HEAD refs/heads/other && + rm .git/index && + echo 3 > B && + git add B && + git commit -m 3 && + echo 1 > A && + git add A && + git commit -m 1b && + echo 4 > B && + git add B && + git commit -m 4 +' + +test_expect_success 'rebase --root expects --onto' ' + test_must_fail git rebase --root +' + +test_expect_success 'setup pre-rebase hook' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +echo "\$1,\$2" >.git/PRE-REBASE-INPUT +EOF + chmod +x .git/hooks/pre-rebase +' +cat > expect <<EOF +4 +3 +2 +1 +EOF + +test_expect_success 'rebase --root --onto <newbase>' ' + git checkout -b work && + git rebase --root --onto master && + git log --pretty=tformat:"%s" > rebased && + test_cmp expect rebased +' + +test_expect_success 'pre-rebase got correct input (1)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase --root --onto <newbase> <branch>' ' + git branch work2 other && + git rebase --root --onto master work2 && + git log --pretty=tformat:"%s" > rebased2 && + test_cmp expect rebased2 +' + +test_expect_success 'pre-rebase got correct input (2)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2 +' + +test_expect_success 'rebase -i --root --onto <newbase>' ' + git checkout -b work3 other && + GIT_EDITOR=: git rebase -i --root --onto master && + git log --pretty=tformat:"%s" > rebased3 && + test_cmp expect rebased3 +' + +test_expect_success 'pre-rebase got correct input (3)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase -i --root --onto <newbase> <branch>' ' + git branch work4 other && + GIT_EDITOR=: git rebase -i --root --onto master work4 && + git log --pretty=tformat:"%s" > rebased4 && + test_cmp expect rebased4 +' + +test_expect_success 'pre-rebase got correct input (4)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4 +' + +test_expect_success 'rebase -i -p with linear history' ' + git checkout -b work5 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --pretty=tformat:"%s" > rebased5 && + test_cmp expect rebased5 +' + +test_expect_success 'pre-rebase got correct input (5)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'set up merge history' ' + git checkout other^ && + git checkout -b side && + echo 5 > C && + git add C && + git commit -m 5 && + git checkout other && + git merge side +' + +sed 's/#/ /g' > expect-side <<'EOF' +* Merge branch 'side' into other +|\## +| * 5 +* | 4 +|/## +* 3 +* 2 +* 1 +EOF + +test_expect_success 'rebase -i -p with merge' ' + git checkout -b work6 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --graph --topo-order --pretty=tformat:"%s" > rebased6 && + test_cmp expect-side rebased6 +' + +test_expect_success 'set up second root and merge' ' + git symbolic-ref HEAD refs/heads/third && + rm .git/index && + rm A B C && + echo 6 > D && + git add D && + git commit -m 6 && + git checkout other && + git merge third +' + +sed 's/#/ /g' > expect-third <<'EOF' +* Merge branch 'third' into other +|\## +| * 6 +* | Merge branch 'side' into other +|\ \## +| * | 5 +* | | 4 +|/ /## +* | 3 +|/## +* 2 +* 1 +EOF + +test_expect_success 'rebase -i -p with two roots' ' + git checkout -b work7 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --graph --topo-order --pretty=tformat:"%s" > rebased7 && + test_cmp expect-third rebased7 +' + +test_expect_success 'setup pre-rebase hook that fails' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +false +EOF + chmod +x .git/hooks/pre-rebase +' + +test_expect_success 'pre-rebase hook stops rebase' ' + git checkout -b stops1 other && + GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1 + test 0 = $(git rev-list other...stops1 | wc -l) +' + +test_expect_success 'pre-rebase hook stops rebase -i' ' + git checkout -b stops2 other && + GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2 + test 0 = $(git rev-list other...stops2 | wc -l) +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 6da212825a..bb4cf00d78 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -45,6 +45,7 @@ test_expect_success 'cherry-pick after renaming branch' ' git checkout rename2 && git cherry-pick added && + test $(git rev-parse HEAD^) = $(git rev-parse rename2) && test -f opos && grep "Add extra line at the end" opos @@ -54,6 +55,7 @@ test_expect_success 'revert after renaming branch' ' git checkout rename1 && git revert added && + test $(git rev-parse HEAD^) = $(git rev-parse rename1) && test -f spoo && ! grep "Add extra line at the end" spoo diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index aeb5405cfe..aba53202f8 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -261,6 +261,7 @@ diff --patch-with-stat -r initial..side diff --patch-with-raw -r initial..side diff --name-status dir2 dir diff --no-index --name-status dir2 dir +diff --no-index --name-status -- dir2 dir diff master master^ side EOF diff --git a/t/t4013/diff.diff_--no-index_--name-status_--_dir2_dir b/t/t4013/diff.diff_--no-index_--name-status_--_dir2_dir new file mode 100644 index 0000000000..6756f8de67 --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--name-status_--_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --name-status -- dir2 dir +A dir/sub +$ diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh new file mode 100755 index 0000000000..e4e3e28fc7 --- /dev/null +++ b/t/t4032-diff-inter-hunk-context.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='diff hunk fusing' + +. ./test-lib.sh + +f() { + echo $1 + i=1 + while test $i -le $2 + do + echo $i + i=$(expr $i + 1) + done + echo $3 +} + +t() { + case $# in + 4) hunks=$4; cmd="diff -U$3";; + 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";; + esac + label="$cmd, $1 common $2" + file=f$1 + expected=expected.$file.$3.$hunks + + if ! test -f $file + then + f A $1 B >$file + git add $file + git commit -q -m. $file + f X $1 Y >$file + fi + + test_expect_success "$label: count hunks ($hunks)" " + test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks + " + + test -f $expected && + test_expect_success "$label: check output" " + git $cmd $file | grep -v '^index ' >actual && + test_cmp $expected actual + " +} + +cat <<EOF >expected.f1.0.1 || exit 1 +diff --git a/f1 b/f1 +--- a/f1 ++++ b/f1 +@@ -1,3 +1,3 @@ +-A ++X + 1 +-B ++Y +EOF + +cat <<EOF >expected.f1.0.2 || exit 1 +diff --git a/f1 b/f1 +--- a/f1 ++++ b/f1 +@@ -1 +1 @@ +-A ++X +@@ -3 +3 @@ A +-B ++Y +EOF + +# common lines ctx intrctx hunks +t 1 line 0 2 +t 1 line 0 0 2 +t 1 line 0 1 1 +t 1 line 0 2 1 +t 1 line 1 1 + +t 2 lines 0 2 +t 2 lines 0 0 2 +t 2 lines 0 1 2 +t 2 lines 0 2 1 +t 2 lines 1 1 + +t 3 lines 1 2 +t 3 lines 1 0 2 +t 3 lines 1 1 1 +t 3 lines 1 2 1 + +t 9 lines 3 2 +t 9 lines 3 2 2 +t 9 lines 3 3 1 + +test_done diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh new file mode 100755 index 0000000000..adfcbb5a3b --- /dev/null +++ b/t/t4129-apply-samemode.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +test_description='applying patch with mode bits' + +. ./test-lib.sh + +test_expect_success setup ' + echo original >file && + git add file && + test_tick && + git commit -m initial && + git tag initial && + echo modified >file && + git diff --stat -p >patch-0.txt && + chmod +x file && + git diff --stat -p >patch-1.txt +' + +test_expect_success 'same mode (no index)' ' + git reset --hard && + chmod +x file && + git apply patch-0.txt && + test -x file +' + +test_expect_success 'same mode (with index)' ' + git reset --hard && + chmod +x file && + git add file && + git apply --index patch-0.txt && + test -x file && + git diff --exit-code +' + +test_expect_success 'same mode (index only)' ' + git reset --hard && + chmod +x file && + git add file && + git apply --cached patch-0.txt && + git ls-files -s file | grep "^100755" +' + +test_expect_success 'mode update (no index)' ' + git reset --hard && + git apply patch-1.txt && + test -x file +' + +test_expect_success 'mode update (with index)' ' + git reset --hard && + git apply --index patch-1.txt && + test -x file && + git diff --exit-code +' + +test_expect_success 'mode update (index only)' ' + git reset --hard && + git apply --cached patch-1.txt && + git ls-files -s file | grep "^100755" +' + +test_done diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index da9588645c..c236b5e83b 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -51,17 +51,29 @@ test_expect_success 'clone remote repository' ' git clone $HTTPD_URL/test_repo.git test_repo_clone ' -test_expect_failure 'push to remote repository' ' +test_expect_failure 'push to remote repository with packed refs' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && test_tick && git commit -m path2 && + HEAD=$(git rev-parse --verify HEAD) && git push && - [ -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/refs/heads/master" ] + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_failure 'create and delete remote branch' ' +test_expect_success ' push to remote repository with unpacked refs' ' + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + rm packed-refs && + git update-ref refs/heads/master \ + 0c973ae9bd51902a28466f3850b543fa66a6aaf4) && + git push && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + +test_expect_success 'create and delete remote branch' ' cd "$ROOT_PATH"/test_repo_clone && git checkout -b dev && : >path3 && @@ -76,6 +88,12 @@ test_expect_failure 'create and delete remote branch' ' test_must_fail git show-ref --verify refs/remotes/origin/dev ' +test_expect_success 'MKCOL sends directory names with trailing slashes' ' + + ! grep "\"MKCOL.*[^/] HTTP/[^ ]*\"" < "$HTTPD_ROOT_PATH"/access.log + +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 78a3fa639c..fe287d31fb 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -125,4 +125,23 @@ test_expect_success 'clone to destination with extra trailing /' ' ' +test_expect_success 'clone to an existing empty directory' ' + mkdir target-3 && + git clone src target-3 && + T=$( cd target-3 && git rev-parse HEAD ) && + S=$( cd src && git rev-parse HEAD ) && + test "$T" = "$S" +' + +test_expect_success 'clone to an existing non-empty directory' ' + mkdir target-4 && + >target-4/Fakefile && + test_must_fail git clone src target-4 +' + +test_expect_success 'clone to an existing path' ' + >target-5 && + test_must_fail git clone src target-5 +' + test_done diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh new file mode 100755 index 0000000000..a8f4419e61 --- /dev/null +++ b/t/t5704-bundle.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description='some bundle related tests' +. ./test-lib.sh + +test_expect_success 'setup' ' + + : > file && + git add file && + test_tick && + git commit -m initial && + test_tick && + git tag -m tag tag && + : > file2 && + git add file2 && + : > file3 && + test_tick && + git commit -m second && + git add file3 && + test_tick && + git commit -m third + +' + +test_expect_success 'tags can be excluded by rev-list options' ' + + git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 && + git ls-remote bundle > output && + ! grep tag output + +' + +test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 802d0d06eb..129fa3000c 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -97,4 +97,27 @@ test_expect_success 'refuse to merge binary files' ' merge.err ' +test_expect_success 'mark rename/delete as unmerged' ' + + git reset --hard && + git checkout -b delete && + git rm a1 && + test_tick && + git commit -m delete && + git checkout -b rename HEAD^ && + git mv a1 a2 + test_tick && + git commit -m rename && + test_must_fail git merge delete && + test 1 = $(git ls-files --unmerged | wc -l) && + git rev-parse --verify :2:a2 && + test_must_fail git rev-parse --verify :3:a2 && + git checkout -f delete && + test_must_fail git merge rename && + test 1 = $(git ls-files --unmerged | wc -l) && + test_must_fail git rev-parse --verify :2:a2 && + git rev-parse --verify :3:a2 + +' + test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 575ef5beb2..ef2e78f9df 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -39,6 +39,31 @@ test_expect_success \ grep "^R100..*path1/COPYING..*path0/COPYING"' test_expect_success \ + 'checking -k on non-existing file' \ + 'git mv -k idontexist path0' + +test_expect_success \ + 'checking -k on untracked file' \ + 'touch untracked1 && + git mv -k untracked1 path0 && + test -f untracked1 && + test ! -f path0/untracked1' + +test_expect_success \ + 'checking -k on multiple untracked files' \ + 'touch untracked2 && + git mv -k untracked1 untracked2 path0 && + test -f untracked1 && + test -f untracked2 && + test ! -f path0/untracked1 + test ! -f path0/untracked2' + +# clean up the mess in case bad things happen +rm -f idontexist untracked1 untracked2 \ + path0/idontexist path0/untracked1 path0/untracked2 \ + .git/index.lock + +test_expect_success \ 'adding another file' \ 'cp "$TEST_DIRECTORY"/../README path0/README && git add path0/README && diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 18fe6f2d57..c4938544d4 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -161,7 +161,14 @@ test_expect_success 'log grep (6)' ' git log --author=-0700 --pretty=tformat:%s >actual && >expect && test_cmp expect actual +' +test_expect_success 'grep with CE_VALID file' ' + git update-index --assume-unchanged t/t && + rm t/t && + test "$(git grep --no-ext-grep t)" = "t/t:test" && + git update-index --no-assume-unchanged t/t && + git checkout t/t ' test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index b0a9d7d536..8537bf9160 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -262,4 +262,12 @@ test_expect_success 'Tag name filtering allows slashes in tag names' ' test_cmp expect actual ' +test_expect_success 'Prune empty commits' ' + git rev-list HEAD > expect && + make_commit to_remove && + git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD && + git rev-list HEAD > actual && + test_cmp expect actual +' + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index be73f7b60a..2ec7ac6a51 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -209,4 +209,29 @@ test_expect_success 'update --init' ' ' +test_expect_success 'do not add files from a submodule' ' + + git reset --hard && + test_must_fail git add init/a + +' + +test_expect_success 'gracefully add submodule with a trailing slash' ' + + git reset --hard && + git commit -m "commit subproject" init && + (cd init && + echo b > a) && + git add init/ && + git diff --exit-code --cached init && + commit=$(cd init && + git commit -m update a >/dev/null && + git rev-parse HEAD) && + git add init/ && + test_must_fail git diff --exit-code --cached init && + test $commit = $(git ls-files --stage | + sed -n "s/^160000 \([^ ]*\).*/\1/p") + +' + test_done diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 6e18a96319..5998baf27b 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -149,10 +149,7 @@ EOF test_expect_success '--signoff' ' echo "yet another content *narf*" >> foo && - echo "zort" | ( - test_set_editor "$TEST_DIRECTORY"/t7500/add-content && - git commit -s -F - foo - ) && + echo "zort" | git commit -s -F - foo && git cat-file commit HEAD | sed "1,/^$/d" > output && test_cmp expect output ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 63bfc6d8b3..b4e2b4db84 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -127,6 +127,26 @@ test_expect_success \ "showing committed revisions" \ "git rev-list HEAD >current" +cat >editor <<\EOF +#!/bin/sh +sed -e "s/good/bad/g" < "$1" > "$1-" +mv "$1-" "$1" +EOF +chmod 755 editor + +cat >msg <<EOF +A good commit message. +EOF + +test_expect_success \ + 'editor not invoked if -F is given' ' + echo "moo" >file && + VISUAL=./editor git commit -a -F msg && + git show -s --pretty=format:"%s" | grep -q good && + echo "quack" >file && + echo "Another good message." | VISUAL=./editor git commit -a -F - && + git show -s --pretty=format:"%s" | grep -q good + ' # We could just check the head sha1, but checking each commit makes it # easier to isolate bugs. diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh new file mode 100755 index 0000000000..49f4e1599a --- /dev/null +++ b/t/t7607-merge-overwrite.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='git-merge + +Do not overwrite changes.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo c0 > c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 > c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 > c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 && + git reset --hard c1 && + echo "c1 a" > c1.c && + git add c1.c && + git commit -m "c1 a" && + git tag c1a && + echo "VERY IMPORTANT CHANGES" > important +' + +test_expect_success 'will not overwrite untracked file' ' + git reset --hard c1 && + cat important > c2.c && + ! git merge c2 && + test_cmp important c2.c +' + +test_expect_success 'will not overwrite new file' ' + git reset --hard c1 && + cat important > c2.c && + git add c2.c && + ! git merge c2 && + test_cmp important c2.c +' + +test_expect_success 'will not overwrite staged changes' ' + git reset --hard c1 && + cat important > c2.c && + git add c2.c && + rm c2.c && + ! git merge c2 && + git checkout c2.c && + test_cmp important c2.c +' + +test_expect_success 'will not overwrite removed file' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cat important > c1.c && + ! git merge c1a && + test_cmp important c1.c +' + +test_expect_success 'will not overwrite re-added file' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cat important > c1.c && + git add c1.c && + ! git merge c1a && + test_cmp important c1.c +' + +test_expect_success 'will not overwrite removed file with staged changes' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cat important > c1.c && + git add c1.c && + rm c1.c && + ! git merge c1a && + git checkout c1.c && + test_cmp important c1.c +' + +test_done diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh new file mode 100755 index 0000000000..b8fb277562 --- /dev/null +++ b/t/t9130-git-svn-authors-file.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# +# Copyright (c) 2008 Eric Wong +# + +test_description='git svn authors file tests' + +. ./lib-git-svn.sh + +cat > svn-authors <<EOF +aa = AAAAAAA AAAAAAA <aa@example.com> +bb = BBBBBBB BBBBBBB <bb@example.com> +EOF + +test_expect_success 'setup svnrepo' ' + for i in aa bb cc dd + do + svn mkdir -m $i --username $i "$svnrepo"/$i + done + ' + +test_expect_success 'start import with incomplete authors file' ' + ! git svn clone --authors-file=svn-authors "$svnrepo" x + ' + +test_expect_success 'imported 2 revisions successfully' ' + ( + cd x + test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 2 && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author BBBBBBB BBBBBBB <bb@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ + grep "^author AAAAAAA AAAAAAA <aa@example\.com> " + ) + ' + +cat >> svn-authors <<EOF +cc = CCCCCCC CCCCCCC <cc@example.com> +dd = DDDDDDD DDDDDDD <dd@example.com> +EOF + +test_expect_success 'continues to import once authors have been added' ' + ( + cd x + git svn fetch --authors-file=../svn-authors && + test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 4 && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author DDDDDDD DDDDDDD <dd@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ + grep "^author CCCCCCC CCCCCCC <cc@example\.com> " + ) + ' + +test_expect_success 'authors-file against globs' ' + svn mkdir -m globs --username aa \ + "$svnrepo"/aa/trunk "$svnrepo"/aa/branches "$svnrepo"/aa/tags && + git svn clone --authors-file=svn-authors -s "$svnrepo"/aa aa-work && + for i in bb ee cc + do + branch="aa/branches/$i" + svn mkdir -m "$branch" --username $i "$svnrepo/$branch" + done + ' + +test_expect_success 'fetch fails on ee' ' + ( cd aa-work && ! git svn fetch --authors-file=../svn-authors ) + ' + +tmp_config_get () { + GIT_CONFIG=.git/svn/.metadata git config --get "$1" +} + +test_expect_success 'failure happened without negative side effects' ' + ( + cd aa-work && + test 6 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && + test 6 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + ) + ' + +cat >> svn-authors <<EOF +ee = EEEEEEE EEEEEEE <ee@example.com> +EOF + +test_expect_success 'fetch continues after authors-file is fixed' ' + ( + cd aa-work && + git svn fetch --authors-file=../svn-authors && + test 8 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && + test 8 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + ) + ' + +test_done diff --git a/transport.c b/transport.c index 56831c57c5..9ad4a16c31 100644 --- a/transport.c +++ b/transport.c @@ -50,9 +50,7 @@ static int read_loose_refs(struct strbuf *path, int name_offset, memset (&list, 0, sizeof(list)); while ((de = readdir(dir))) { - if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || - (de->d_name[1] == '.' && - de->d_name[2] == '\0'))) + if (is_dot_or_dotdot(de->d_name)) continue; ALLOC_GROW(list.entries, list.nr + 1, list.alloc); list.entries[list.nr++] = xstrdup(de->d_name); diff --git a/unpack-trees.c b/unpack-trees.c index 54f301da67..15c9ef592b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -494,7 +494,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * anything in the existing directory there. */ int namelen; - int pos, i; + int i; struct dir_struct d; char *pathbuf; int cnt = 0; @@ -515,24 +515,20 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - pos = index_name_pos(o->src_index, ce->name, namelen); - if (0 <= pos) - return cnt; /* we have it as nondirectory */ - pos = -pos - 1; - for (i = pos; i < o->src_index->cache_nr; i++) { - struct cache_entry *ce = o->src_index->cache[i]; - int len = ce_namelen(ce); + for (i = o->pos; i < o->src_index->cache_nr; i++) { + struct cache_entry *ce2 = o->src_index->cache[i]; + int len = ce_namelen(ce2); if (len < namelen || - strncmp(ce->name, ce->name, namelen) || - ce->name[namelen] != '/') + strncmp(ce->name, ce2->name, namelen) || + ce2->name[namelen] != '/') break; /* - * ce->name is an entry in the subdirectory. + * ce2->name is an entry in the subdirectory. */ - if (!ce_stage(ce)) { - if (verify_uptodate(ce, o)) + if (!ce_stage(ce2)) { + if (verify_uptodate(ce2, o)) return -1; - add_entry(o, ce, CE_REMOVE, 0); + add_entry(o, ce2, CE_REMOVE, 0); } cnt++; } @@ -588,7 +584,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, return 0; if (!lstat(ce->name, &st)) { - int cnt; + int ret; int dtype = ce_to_dtype(ce); struct cache_entry *result; @@ -616,13 +612,15 @@ static int verify_absent(struct cache_entry *ce, const char *action, * files that are in "foo/" we would lose * it. */ - cnt = verify_clean_subdirectory(ce, action, o); + ret = verify_clean_subdirectory(ce, action, o); + if (ret < 0) + return ret; /* * If this removed entries from the index, * what that means is: * - * (1) the caller unpack_trees_rec() saw path/foo + * (1) the caller unpack_callback() saw path/foo * in the index, and it has not removed it because * it thinks it is handling 'path' as blob with * D/F conflict; @@ -635,7 +633,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, * We need to increment it by the number of * deleted entries here. */ - o->pos += cnt; + o->pos += ret; return 0; } diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 84fff583e2..361f802319 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -84,6 +84,7 @@ typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long typedef struct s_xdemitconf { long ctxlen; + long interhunkctxlen; unsigned long flags; find_func_t find_func; void *find_func_priv; diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 4625c1b421..05bfa41f10 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -59,9 +59,10 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * */ xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) - if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) break; return xchp; |
