diff options
103 files changed, 1912 insertions, 864 deletions
diff --git a/Documentation/RelNotes-1.6.1.3.txt b/Documentation/RelNotes-1.6.1.3.txt new file mode 100644 index 0000000000..6f0bde156a --- /dev/null +++ b/Documentation/RelNotes-1.6.1.3.txt @@ -0,0 +1,32 @@ +GIT v1.6.1.3 Release Notes +========================== + +Fixes since v1.6.1.2 +-------------------- + +* "git diff --binary | git apply" pipeline did not work well when + a binary blob is changed to a symbolic link. + +* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did + not work as expected. + +* "git grep" did not pass the -I (ignore binary) option when + calling out an external grep program. + +* "git log" and friends include HEAD to the set of starting points + when --all is given. This makes a difference when you are not + on any branch. + +* "git mv" to move an untracked file to overwrite a tracked + contents misbehaved. + +* "git merge -s octopus" with many potential merge bases did not + work correctly. + +* RPM binary package installed the html manpages in a wrong place. + +Also includes minor documentation fixes and updates. + + +-- +git shortlog --no-merges v1.6.1.2-33-gc789350.. diff --git a/Documentation/RelNotes-1.6.1.4.txt b/Documentation/RelNotes-1.6.1.4.txt new file mode 100644 index 0000000000..a9f1a6b8b5 --- /dev/null +++ b/Documentation/RelNotes-1.6.1.4.txt @@ -0,0 +1,19 @@ +GIT v1.6.1.4 Release Notes +========================== + +Fixes since v1.6.1.3 +-------------------- + +* "git fast-export" produced wrong output with some parents missing from + commits, when the history is clock-skewed. + +* "git fast-import" sometimes failed to read back objects it just wrote + out and aborted, because it failed to flush stale cached data. + +* "git repack" did not error out when necessary object was missing in the + repository. + +Also includes minor documentation fixes and updates. + +-- +git shortlog --no-merges v1.6.1.3.. diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt index 3151c85d88..6ed31595ba 100644 --- a/Documentation/RelNotes-1.6.2.txt +++ b/Documentation/RelNotes-1.6.2.txt @@ -1,6 +1,20 @@ GIT v1.6.2 Release Notes ======================== +With the next major release, "git push" into a branch that is +currently checked out will be refused by default. You can choose +what should happen upon such a push by setting the configuration +variable receive.denyCurrentBranch in the receiving repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the transition plan. + + Updates since v1.6.1 -------------------- @@ -11,7 +25,9 @@ Updates since v1.6.1 * gitweb updates, including a new patch view and RSS/Atom feed improvements. -(portability) +* (contrib/emacs) git.el now has commands for checking out a branch, + creating a branch, cherry-picking and reverting commits; vc-git.el + is not shipped with git anymore (it is part of official Emacs). (performance) @@ -29,12 +45,22 @@ Updates since v1.6.1 * "git add -p" learned 'g'oto action to jump directly to a hunk. +* "git add -p" learned to find a hunk with given text with '/'. + +* "git add -p" optionally can be told to work with just the command letter + without Enter. + * when "git am" stops upon a patch that does not apply, it shows the title of the offending patch. * "git am --directory=<dir>" and "git am --reject" passes these options to underlying "git apply". +* "git am" learned --ignore-date option. + +* "git blame" aligns author names better when they are spelled in + non US-ASCII encoding. + * "git clone" now makes its best effort when cloning from an empty repository to set up configuration variables to refer to the remote repository. @@ -48,6 +74,9 @@ Updates since v1.6.1 the commit log message it serves via gitcvs.commitmsgannotation configuration. +* "git cvsserver" learned to handle 'noop' command some CVS clients seem + to expect to work. + * "git diff" learned a new option --inter-hunk-context to coalesce close hunks together and show context between them. @@ -56,20 +85,13 @@ Updates since v1.6.1 * "git diff" learned --patience to run "patience diff" algorithm. -* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did - not work as expected. - * "git filter-branch" learned --prune-empty option that discards commits that do not change the contents. -* "git grep -w" and "git grep" for fixed strings have been optimized. - -* "git log" and friends include HEAD to the set of starting points - when --all is given. This makes a difference when you are not on - any branch. +* "git fsck" now checks loose objects in alternate object stores, instead + of misreporting them as missing. -* "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 grep -w" and "git grep" for fixed strings have been optimized. * "git mergetool" learned -y(--no-prompt) option to disable prompting. @@ -80,7 +102,8 @@ Updates since v1.6.1 "git checkout" switches branches, taking the local changes while switching to another commit. -(internal) +* "git tag" learned --contains that works the same way as the same option + from "git branch". Fixes since v1.6.1 @@ -89,18 +112,34 @@ Fixes since v1.6.1 All of the fixes in v1.6.1.X maintenance series are included in this release, unless otherwise noted. +Here are fixes that this release has, but have not been backported to +v1.6.1.X series. + * "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 bundle" did not exclude annotated tags even when a range given + from the command line wanted to. + +* "git filter-branch" unnecessarily refused to work when you had + checked out a different commit from what is recorded in the superproject + index in a submodule. + +* "git filter-branch" incorrectly tried to update a nonexistent work tree + at the end when it is run in a bare repository. + +* "git mergetool" used to ignore autocrlf and other attributes + based content rewriting. * branch switching and merges had a silly bug that did not validate the correct directory when making sure an existing subdirectory is clean. +* "git -p cmd" when cmd is not a built-in one left the display in funny state + when killed in the middle. + -- exec >/var/tmp/1 -O=v1.6.1.2-252-g8c95d3c +O=v1.6.1.3-371-gc19923a echo O=$(git describe master) git shortlog --no-merges $O..master ^maint diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index ba07c8c571..9b559adefc 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -376,9 +376,36 @@ Thunderbird (A Large Angry SCM) +By default, Thunderbird will both wrap emails as well as flag them as +being 'format=flowed', both of which will make the resulting email unusable +by git. + Here are some hints on how to successfully submit patches inline using Thunderbird. +There are two different approaches. One approach is to configure +Thunderbird to not mangle patches. The second approach is to use +an external editor to keep Thunderbird from mangling the patches. + +Approach #1 (configuration): + +This recipe is current as of Thunderbird 2.0.0.19. Three steps: + 1. Configure your mail server composition as plain text + Edit...Account Settings...Composition & Addressing, + uncheck 'Compose Messages in HTML'. + 2. Configure your general composition window to not wrap + Edit..Preferences..Composition, wrap plain text messages at 0 + 3. Disable the use of format=flowed + Edit..Preferences..Advanced..Config Editor. Search for: + mailnews.send_plaintext_flowed + toggle it to make sure it is set to 'false'. + +After that is done, you should be able to compose email as you +otherwise would (cut + paste, git-format-patch | git-imap-send, etc), +and the patches should not be mangled. + +Approach #2 (external editor): + This recipe appears to work with the current [*1*] Thunderbird from Suse. The following Thunderbird extensions are needed: diff --git a/Documentation/config.txt b/Documentation/config.txt index 6dd57bd52c..f5152c5038 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -556,8 +556,8 @@ color.interactive:: color.interactive.<slot>:: Use customized color for 'git-add --interactive' - output. `<slot>` may be `prompt`, `header`, or `help`, for - three distinct types of normal output from interactive + output. `<slot>` may be `prompt`, `header`, `help` or `error`, for + four distinct types of normal output from interactive programs. The values of these variables may be specified as in color.branch.<slot>. @@ -1000,6 +1000,13 @@ instaweb.port:: The port number to bind the gitweb httpd to. See linkgit:git-instaweb[1]. +interactive.singlekey:: + In interactive programs, allow the user to provide one-letter + input with a single key (i.e., without hitting enter). + Currently this is used only by the `\--patch` mode of + linkgit:git-add[1]. Note that this setting is silently + ignored if portable keystroke input is not available. + log.date:: Set default date-time mode for the log command. Setting log.date value is similar to using 'git-log'\'s --date option. The value is one of the diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index ea0f6a0f3a..57590b1480 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -107,17 +107,17 @@ incremental bundle, ---------------- machineA$ cd R1 -machineA$ git bundle create file.bdl master +machineA$ git bundle create file.bundle master machineA$ git tag -f lastR2bundle master ---------------- -Then you sneakernet file.bdl to the target machine B. Because you don't +Then you sneakernet file.bundle to the target machine B. Because you don't have to have any object to extract objects from such a bundle, not only you can fetch/pull from a bundle, you can clone from it as if it was a remote repository. ---------------- -machineB$ git clone /home/me/tmp/file.bdl R2 +machineB$ git clone /home/me/tmp/file.bundle R2 ---------------- This will define a remote called "origin" in the resulting repository that @@ -126,12 +126,12 @@ have an entry like this: ------------------------ [remote "origin"] - url = /home/me/tmp/file.bdl + url = /home/me/tmp/file.bundle fetch = refs/heads/*:refs/remotes/origin/* ------------------------ You can fetch/pull to update the resulting mine.git repository after -replacing the bundle you store at /home/me/tmp/file.bdl with incremental +replacing the bundle you store at /home/me/tmp/file.bundle with incremental updates from here on. After working more in the original repository, you can create an @@ -139,11 +139,11 @@ incremental bundle to update the other: ---------------- machineA$ cd R1 -machineA$ git bundle create file.bdl lastR2bundle..master +machineA$ git bundle create file.bundle lastR2bundle..master machineA$ git tag -f lastR2bundle master ---------------- -and sneakernet it to the other machine to replace /home/me/tmp/file.bdl, +and sneakernet it to the other machine to replace /home/me/tmp/file.bundle, and pull from it. ---------------- diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 451950bab6..7ffe03f427 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -212,6 +212,11 @@ git filter-branch --index-filter 'git rm --cached filename' HEAD Now, you will get the rewritten history saved in HEAD. +As with using `rm filename`, `git rm --cached filename` will fail +if the file is absent from the tree of a commit. If it is not important +whether the file is already absent from the tree, you can use +`git rm --cached --ignore-unmatch filename` instead. + To rewrite the repository to look as if `foodir/` had been its project root, and discard all other history: @@ -334,6 +339,47 @@ git filter-branch --index-filter \ --------------------------------------------------------------- + +Checklist for Shrinking a Repository +------------------------------------ + +git-filter-branch is often used to get rid of a subset of files, +usually with some combination of `\--index-filter` and +`\--subdirectory-filter`. People expect the resulting repository to +be smaller than the original, but you need a few more steps to +actually make it smaller, because git tries hard not to lose your +objects until you tell it to. First make sure that: + +* You really removed all variants of a filename, if a blob was moved + over its lifetime. `git log \--name-only \--follow \--all \-- + filename` can help you find renames. + +* You really filtered all refs: use `\--tag-name-filter cat \-- + \--all` when calling git-filter-branch. + +Then there are two ways to get a smaller repository. A safer way is +to clone, that keeps your original intact. + +* Clone it with `git clone +++file:///path/to/repo+++`. The clone + will not have the removed objects. See linkgit:git-clone[1]. (Note + that cloning with a plain path just hardlinks everything!) + +If you really don't want to clone it, for whatever reasons, check the +following points instead (in this order). This is a very destructive +approach, so *make a backup* or go back to cloning it. You have been +warned. + +* Remove the original refs backed up by git-filter-branch: say `git + for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git + update-ref -d`. + +* Expire all reflogs with `git reflog expire \--expire=now \--all`. + +* Garbage collect all unreferenced objects with `git gc \--prune=now` + (or if your git-gc is not new enough to support arguments to + `\--prune`, use `git repack -ad; git prune` instead). + + Author ------ Written by Petr "Pasky" Baudis <pasky@suse.cz>, diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 7086eea74a..b292e9843a 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository SYNOPSIS -------- -'git gc' [--aggressive] [--auto] [--quiet] +'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] DESCRIPTION ----------- @@ -59,6 +59,14 @@ are consolidated into a single pack by using the `-A` option of 'git-repack'. Setting `gc.autopacklimit` to 0 disables automatic consolidation of packs. +--prune=<date>:: + Prune loose objects older than date (default is 2 weeks ago, + overrideable by the config variable `gc.pruneExpire`). This + option is on by default. + +--no-prune:: + Do not prune any loose objects. + --quiet:: Suppress all progress reports. diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index bd49a0aee8..1685f04efe 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -98,6 +98,20 @@ Using direct mode with SSL: .......................... +CAUTION +------- +It is still your responsibility to make sure that the email message +sent by your email program meets the standards of your project. +Many projects do not like patches to be attached. Some mail +agents will transform patches (e.g. wrap lines, send them as +format=flowed) in ways that make them fail. You will get angry +flames ridiculing you if you don't check this. + +Thunderbird in particular is known to be problematic. Thunderbird +users may wish to visit this web page for more information: + http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email + + BUGS ---- Doesn't handle lines starting with "From " in the message body. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 3d6d429e5e..30487de48f 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -246,6 +246,7 @@ OPTIONS --whitespace=<nowarn|warn|error|error-all|strip>:: This flag is passed to the 'git-apply' program (see linkgit:git-apply[1]) that applies the patch. + Incompatible with the --interactive option. -i:: --interactive:: diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 7b654f7928..3d456545d7 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -499,6 +499,14 @@ svn-remote.<name>.rewriteRoot:: the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. +svn.brokenSymlinkWorkaround:: +This disables potentially expensive checks to workaround broken symlinks +checked into SVN by broken clients. Set this option to "false" if you +track a SVN repository with many empty blobs that are not symlinks. +This option may be changed while "git-svn" is running and take effect on +the next revision fetched. If unset, git-svn assumes this option to be +"true". + -- Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps diff --git a/Documentation/git.txt b/Documentation/git.txt index cd527c6252..0c7bba3fa9 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,11 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.1.1/git.html[documentation for release 1.6.1.1] +* link:v1.6.1.3/git.html[documentation for release 1.6.1.3] * release notes for + link:RelNotes-1.6.1.3.txt[1.6.1.3], + link:RelNotes-1.6.1.2.txt[1.6.1.2], link:RelNotes-1.6.1.1.txt[1.6.1.1], link:RelNotes-1.6.1.txt[1.6.1]. diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 4673a75a98..bd005bc5c8 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -47,7 +47,8 @@ frequently used options. After an attempt to merge stops with conflicts, show the commits on the history between two branches (i.e. the HEAD and the MERGE_HEAD) - that modify the conflicted files. + that modify the conflicted files and do not exist on all the heads + being merged. --argscmd=<command>:: Command to be run each time gitk has to determine the list of @@ -228,7 +228,7 @@ GITWEB_FAVICON = git-favicon.png GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = -export prefix bindir sharedir htmldir sysconfdir +export prefix bindir sharedir sysconfdir CC = gcc AR = ar @@ -317,8 +317,8 @@ PROGRAMS += git-var$X # builtin-$C.o but is linked in as part of some other command. BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) -BUILT_INS += git-cherry-pick$X BUILT_INS += git-cherry$X +BUILT_INS += git-cherry-pick$X BUILT_INS += git-format-patch$X BUILT_INS += git-fsck-objects$X BUILT_INS += git-get-tar-commit-id$X @@ -357,8 +357,8 @@ LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h -LIB_H += compat/mingw.h LIB_H += compat/cygwin.h +LIB_H += compat/mingw.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -383,7 +383,6 @@ LIB_H += pack-refs.h LIB_H += pack-revindex.h LIB_H += parse-options.h LIB_H += patch-ids.h -LIB_H += string-list.h LIB_H += pkt-line.h LIB_H += progress.h LIB_H += quote.h @@ -397,6 +396,7 @@ LIB_H += sha1-lookup.h LIB_H += sideband.h LIB_H += sigchain.h LIB_H += strbuf.h +LIB_H += string-list.h LIB_H += tag.h LIB_H += transport.h LIB_H += tree.h @@ -435,8 +435,8 @@ LIB_OBJS += diffcore-order.o LIB_OBJS += diffcore-pickaxe.o LIB_OBJS += diffcore-rename.o LIB_OBJS += diff-delta.o -LIB_OBJS += diff-no-index.o LIB_OBJS += diff-lib.o +LIB_OBJS += diff-no-index.o LIB_OBJS += diff.o LIB_OBJS += dir.o LIB_OBJS += editor.o @@ -468,9 +468,9 @@ LIB_OBJS += pager.o LIB_OBJS += parse-options.o LIB_OBJS += patch-delta.o LIB_OBJS += patch-ids.o -LIB_OBJS += string-list.o LIB_OBJS += path.o LIB_OBJS += pkt-line.o +LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += progress.o LIB_OBJS += quote.o @@ -484,13 +484,14 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o -LIB_OBJS += sha1_file.o LIB_OBJS += sha1-lookup.o +LIB_OBJS += sha1_file.o LIB_OBJS += sha1_name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o +LIB_OBJS += string-list.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o @@ -499,8 +500,8 @@ LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o LIB_OBJS += unpack-trees.o -LIB_OBJS += userdiff.o LIB_OBJS += usage.o +LIB_OBJS += userdiff.o LIB_OBJS += utf8.o LIB_OBJS += walker.o LIB_OBJS += wrapper.o @@ -508,7 +509,6 @@ LIB_OBJS += write_or_die.o LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o -LIB_OBJS += preload-index.o BUILTIN_OBJS += builtin-add.o BUILTIN_OBJS += builtin-annotate.o @@ -649,10 +649,12 @@ endif ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease - ifneq ($(shell expr "$(uname_R)" : '9\.'),2) + ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) OLD_ICONV = UnfortunatelyYes endif - NO_STRLCPY = YesPlease + ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) + NO_STRLCPY = YesPlease + endif NO_MEMMEM = YesPlease THREADED_DELTA_SEARCH = YesPlease endif @@ -1434,14 +1436,14 @@ remove-dashes: ### Installation rules -ifeq ($(abspath $(template_dir)),$(template_dir)) +ifneq ($(filter /%,$(firstword $(template_dir))),) template_instdir = $(template_dir) else template_instdir = $(prefix)/$(template_dir) endif export template_instdir -ifeq ($(abspath $(gitexecdir)),$(gitexecdir)) +ifneq ($(filter /%,$(firstword $(gitexecdir))),) gitexec_instdir = $(gitexecdir) else gitexec_instdir = $(prefix)/$(gitexecdir) @@ -132,7 +132,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0); if (err) return err; - return READ_TREE_RECURSIVE; + return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } buffer = sha1_file_to_archive(path_without_prefix, sha1, mode, @@ -103,14 +103,22 @@ void create_branch(const char *head, struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; + char *real_ref, msg[PATH_MAX + 20]; + struct strbuf ref = STRBUF_INIT; int forcing = 0; + int len; - snprintf(ref, sizeof ref, "refs/heads/%s", name); - if (check_ref_format(ref)) + len = strlen(name); + if (interpret_nth_last_branch(name, &ref) != len) { + strbuf_reset(&ref); + strbuf_add(&ref, name, len); + } + strbuf_splice(&ref, 0, 0, "refs/heads/", 11); + + if (check_ref_format(ref.buf)) die("'%s' is not a valid branch name.", name); - if (resolve_ref(ref, sha1, 1, NULL)) { + if (resolve_ref(ref.buf, sha1, 1, NULL)) { if (!force) die("A branch named '%s' already exists.", name); else if (!is_bare_repository() && !strcmp(head, name)) @@ -142,7 +150,7 @@ void create_branch(const char *head, die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); - lock = lock_any_ref_for_update(ref, NULL, 0); + lock = lock_any_ref_for_update(ref.buf, NULL, 0); if (!lock) die("Failed to lock ref for update: %s.", strerror(errno)); @@ -162,6 +170,7 @@ void create_branch(const char *head, if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); + strbuf_release(&ref); free(real_ref); } diff --git a/builtin-blame.c b/builtin-blame.c index f3be9fa9a5..114a214ed3 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -19,6 +19,7 @@ #include "string-list.h" #include "mailmap.h" #include "parse-options.h" +#include "utf8.h" static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file"; @@ -1630,13 +1631,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) printf(" %*d", max_orig_digits, ent->s_lno + 1 + cnt); - if (!(opt & OUTPUT_NO_AUTHOR)) - printf(" (%-*.*s %10s", - longest_author, longest_author, - ci.author, + if (!(opt & OUTPUT_NO_AUTHOR)) { + int pad = longest_author - utf8_strwidth(ci.author); + printf(" (%s%*s %10s", + ci.author, pad, "", format_time(ci.author_time, ci.author_tz, show_raw_time)); + } printf(" %*d) ", max_digits, ent->lno + 1 + cnt); } @@ -1767,7 +1769,7 @@ static void find_alignment(struct scoreboard *sb, int *option) if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - num = strlen(ci.author); + num = utf8_strwidth(ci.author); if (longest_author < num) longest_author = num; } diff --git a/builtin-branch.c b/builtin-branch.c index 56a1971d69..504a981ad5 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -99,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) const char *fmt, *remote; int i; int ret = 0; + struct strbuf bname = STRBUF_INIT; switch (kinds) { case REF_REMOTE_BRANCH: @@ -119,20 +120,25 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!head_rev) die("Couldn't look up commit object for HEAD"); } - for (i = 0; i < argc; i++) { - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) { + for (i = 0; i < argc; i++, strbuf_release(&bname)) { + int len = strlen(argv[i]); + + if (interpret_nth_last_branch(argv[i], &bname) != len) + strbuf_add(&bname, argv[i], len); + + if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error("Cannot delete the branch '%s' " - "which you are currently on.", argv[i]); + "which you are currently on.", bname.buf); ret = 1; continue; } free(name); - name = xstrdup(mkpath(fmt, argv[i])); + name = xstrdup(mkpath(fmt, bname.buf)); if (!resolve_ref(name, sha1, 1, NULL)) { error("%sbranch '%s' not found.", - remote, argv[i]); + remote, bname.buf); ret = 1; continue; } @@ -152,22 +158,23 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!force && !in_merge_bases(rev, &head_rev, 1)) { error("The branch '%s' is not an ancestor of " - "your current HEAD.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'.", argv[i], argv[i]); + "your current HEAD.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'.", bname.buf, bname.buf); ret = 1; continue; } if (delete_ref(name, sha1, 0)) { error("Error deleting %sbranch '%s'", remote, - argv[i]); + bname.buf); ret = 1; } else { struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (%s).\n", remote, argv[i], - find_unique_abbrev(sha1, DEFAULT_ABBREV)); - strbuf_addf(&buf, "branch.%s", argv[i]); + printf("Deleted %sbranch %s (%s).\n", remote, + bname.buf, + find_unique_abbrev(sha1, DEFAULT_ABBREV)); + strbuf_addf(&buf, "branch.%s", bname.buf); if (git_config_rename_section(buf.buf, NULL) < 0) warning("Update of config-file failed"); strbuf_release(&buf); diff --git a/builtin-clone.c b/builtin-clone.c index f73029e2ba..c338910b1c 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -350,6 +350,19 @@ static struct ref *write_remote_refs(const struct ref *refs, return local_refs; } +static void install_branch_config(const char *local, + const char *origin, + const char *remote) +{ + struct strbuf key = STRBUF_INIT; + strbuf_addf(&key, "branch.%s.remote", local); + git_config_set(key.buf, origin); + strbuf_reset(&key); + strbuf_addf(&key, "branch.%s.merge", local); + git_config_set(key.buf, remote); + strbuf_release(&key); +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int use_local_hardlinks = 1; @@ -539,6 +552,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; + if (!option_bare) + install_branch_config("master", option_origin, + "refs/heads/master"); } if (head_points_at) { @@ -567,11 +583,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) head_points_at->peer_ref->name, reflog_msg.buf); - strbuf_addf(&key, "branch.%s.remote", head); - git_config_set(key.buf, option_origin); - strbuf_reset(&key); - strbuf_addf(&key, "branch.%s.merge", head); - git_config_set(key.buf, head_points_at->name); + install_branch_config(head, option_origin, + head_points_at->name); } } else if (remote_head) { /* Source had detached HEAD pointing somewhere. */ diff --git a/builtin-commit.c b/builtin-commit.c index d6a3a6203a..46e649cd7c 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -561,7 +561,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix) commitable = run_status(fp, index_file, prefix, 1); wt_status_use_color = saved_color_setting; } else { - struct rev_info rev; unsigned char sha1[20]; const char *parent = "HEAD"; @@ -573,16 +572,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (get_sha1(parent, sha1)) commitable = !!active_nr; - else { - init_revisions(&rev, ""); - rev.abbrev = 0; - setup_revisions(0, NULL, &rev, parent); - DIFF_OPT_SET(&rev.diffopt, QUIET); - DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); - run_diff_index(&rev, 1 /* cached */); - - commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); - } + else + commitable = index_differs_from(parent, 0); } fclose(fp); diff --git a/builtin-fast-export.c b/builtin-fast-export.c index e9ee2c79ac..fdf4ae9ebd 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -514,6 +514,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) get_tags_and_duplicates(&revs.pending, &extra_refs); + revs.topo_order = 1; if (prepare_revision_walk(&revs)) die("revision walk setup failed"); revs.diffopt.format_callback = show_filemodify; diff --git a/builtin-gc.c b/builtin-gc.c index a2014388da..8d990ed493 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -161,7 +161,8 @@ static int need_to_gc(void) */ if (too_many_packs()) append_option(argv_repack, - !strcmp(prune_expire, "now") ? "-a" : "-A", + prune_expire && !strcmp(prune_expire, "now") ? + "-a" : "-A", MAX_ADD); else if (!too_many_loose_objects()) return 0; @@ -173,14 +174,15 @@ static int need_to_gc(void) int cmd_gc(int argc, const char **argv, const char *prefix) { - int prune = 0; int aggressive = 0; int auto_gc = 0; int quiet = 0; char buf[80]; struct option builtin_gc_options[] = { - OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"), + { OPTION_STRING, 0, "prune", &prune_expire, "date", + "prune unreferenced objects", + PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"), @@ -218,7 +220,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) "\"git help gc\" for more information.\n"); } else append_option(argv_repack, - !strcmp(prune_expire, "now") ? "-a" : "-A", + prune_expire && !strcmp(prune_expire, "now") + ? "-a" : "-A", MAX_ADD); if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) @@ -230,9 +233,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) return error(FAILED_RUN, argv_repack[0]); - argv_prune[2] = prune_expire; - if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_prune[0]); + if (prune_expire) { + argv_prune[2] = prune_expire; + if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) + return error(FAILED_RUN, argv_prune[0]); + } if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) return error(FAILED_RUN, argv_rerere[0]); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 3434031295..9dec282fba 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -262,6 +262,21 @@ static const char *verify_pathspec(const char *prefix) return max ? xmemdupz(prev, max) : NULL; } +static void strip_trailing_slash_from_submodules(void) +{ + const char **p; + + for (p = pathspec; *p != NULL; p++) { + int len = strlen(*p), pos; + + if (len < 1 || (*p)[len - 1] != '/') + continue; + pos = cache_name_pos(*p, len - 1); + if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode)) + *p = xstrndup(*p, len - 1); + } +} + /* * Read the tree specified with --with-tree option * (typically, HEAD) into stage #1 and then @@ -510,6 +525,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv + i); + /* be nice with submodule patsh ending in a slash */ + read_cache(); + if (pathspec) + strip_trailing_slash_from_submodules(); + /* Verify that the pathspec matches the prefix */ if (pathspec) prefix = verify_pathspec(prefix); @@ -533,7 +553,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) show_killed | show_modified)) show_cached = 1; - read_cache(); if (prefix) prune_cache(prefix); if (with_tree) { diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 5b63e6eada..fca46312f6 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -68,13 +68,8 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, * * Something similar to this incomplete example: * - if (show_subprojects(base, baselen, pathname)) { - struct child_process ls_tree; - - ls_tree.dir = base; - ls_tree.argv = ls-tree; - start_command(&ls_tree); - } + if (show_subprojects(base, baselen, pathname)) + retval = READ_TREE_RECURSIVE; * */ type = commit_type; diff --git a/builtin-merge.c b/builtin-merge.c index 885fad9bba..6d2160d0a3 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -356,9 +356,14 @@ static void merge_name(const char *remote, struct strbuf *msg) struct object *remote_head; unsigned char branch_head[20], buf_sha[20]; struct strbuf buf = STRBUF_INIT; + struct strbuf bname = STRBUF_INIT; const char *ptr; int len, early; + len = strlen(remote); + if (interpret_nth_last_branch(remote, &bname) == len) + remote = bname.buf; + memset(branch_head, 0, sizeof(branch_head)); remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); if (!remote_head) @@ -371,7 +376,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!hashcmp(remote_head->sha1, branch_head)) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", sha1_to_hex(branch_head), remote); - return; + goto cleanup; } /* See if remote matches <name>^^^.. or <name>~<number> */ @@ -411,7 +416,8 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(remote_head->sha1), truname.buf + 11, (early ? " (early part)" : "")); - return; + strbuf_release(&truname); + goto cleanup; } } @@ -432,10 +438,13 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_remove(&line, ptr-line.buf+1, 13); strbuf_addbuf(msg, &line); strbuf_release(&line); - return; + goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", sha1_to_hex(remote_head->sha1), remote); +cleanup: + strbuf_release(&buf); + strbuf_release(&bname); } static int git_merge_config(const char *k, const char *v, void *cb) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 596dfe9223..f7e04c45f9 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -12,19 +12,22 @@ static const char receive_pack_usage[] = "git receive-pack <git-dir>"; enum deny_action { + DENY_UNCONFIGURED, DENY_IGNORE, DENY_WARN, DENY_REFUSE, }; -static int deny_deletes = 0; -static int deny_non_fast_forwards = 0; -static enum deny_action deny_current_branch = DENY_WARN; +static int deny_deletes; +static int deny_non_fast_forwards; +static enum deny_action deny_current_branch = DENY_UNCONFIGURED; +static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; +static const char *head_name; static char capabilities[] = " report-status delete-refs "; static int capabilities_sent; @@ -76,6 +79,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.denydeletecurrent") == 0) { + deny_delete_current = parse_deny_action(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -202,16 +210,67 @@ static int run_update_hook(struct command *cmd) static int is_ref_checked_out(const char *ref) { - unsigned char sha1[20]; - const char *head; - if (is_bare_repository()) return 0; - head = resolve_ref("HEAD", sha1, 0, NULL); - if (!head) + if (!head_name) return 0; - return !strcmp(head, ref); + return !strcmp(head_name, ref); +} + +static char *warn_unconfigured_deny_msg[] = { + "Updating the currently checked out branch may cause confusion,", + "as the index and work tree do not reflect changes that are in HEAD.", + "As a result, you may see the changes you just pushed into it", + "reverted when you run 'git diff' over there, and you may want", + "to run 'git reset --hard' before starting to work to recover.", + "", + "You can set 'receive.denyCurrentBranch' configuration variable to", + "'refuse' in the remote repository to forbid pushing into its", + "current branch." + "", + "To allow pushing into the current branch, you can set it to 'ignore';", + "but this is not recommended unless you arranged to update its work", + "tree to match what you pushed in some other way.", + "", + "To squelch this message, you can set it to 'warn'.", + "", + "Note that the default will change in a future version of git", + "to refuse updating the current branch unless you have the", + "configuration variable set to either 'ignore' or 'warn'." +}; + +static void warn_unconfigured_deny(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) + warning(warn_unconfigured_deny_msg[i]); +} + +static char *warn_unconfigured_deny_delete_current_msg[] = { + "Deleting the current branch can cause confusion by making the next", + "'git clone' not check out any file.", + "", + "You can set 'receive.denyDeleteCurrent' configuration variable to", + "'refuse' in the remote repository to disallow deleting the current", + "branch.", + "", + "You can set it to 'ignore' to allow such a delete without a warning.", + "", + "To make this warning message less loud, you can set it to 'warn'.", + "", + "Note that the default will change in a future version of git", + "to refuse deleting the current branch unless you have the", + "configuration variable set to either 'ignore' or 'warn'." +}; + +static void warn_unconfigured_deny_delete_current(void) +{ + int i; + for (i = 0; + i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i++) + warning(warn_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -227,22 +286,20 @@ static const char *update(struct command *cmd) return "funny refname"; } - switch (deny_current_branch) { - case DENY_IGNORE: - break; - case DENY_WARN: - if (!is_ref_checked_out(name)) + if (is_ref_checked_out(name)) { + switch (deny_current_branch) { + case DENY_IGNORE: break; - warning("updating the currently checked out branch; this may" - " cause confusion,\n" - "as the index and working tree do not reflect changes" - " that are now in HEAD."); - break; - case DENY_REFUSE: - if (!is_ref_checked_out(name)) + case DENY_UNCONFIGURED: + case DENY_WARN: + warning("updating the current branch"); + if (deny_current_branch == DENY_UNCONFIGURED) + warn_unconfigured_deny(); break; - error("refusing to update checked out branch: %s", name); - return "branch is currently checked out"; + case DENY_REFUSE: + error("refusing to update checked out branch: %s", name); + return "branch is currently checked out"; + } } if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) { @@ -250,12 +307,30 @@ static const char *update(struct command *cmd) "but I can't find it!", sha1_to_hex(new_sha1)); return "bad pack"; } - if (deny_deletes && is_null_sha1(new_sha1) && - !is_null_sha1(old_sha1) && - !prefixcmp(name, "refs/heads/")) { - error("denying ref deletion for %s", name); - return "deletion prohibited"; + + if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) { + if (deny_deletes && !prefixcmp(name, "refs/heads/")) { + error("denying ref deletion for %s", name); + return "deletion prohibited"; + } + + if (!strcmp(name, head_name)) { + switch (deny_delete_current) { + case DENY_IGNORE: + break; + case DENY_WARN: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + warn_unconfigured_deny_delete_current(); + warning("deleting the current branch"); + break; + case DENY_REFUSE: + error("refusing to delete the current branch: %s", name); + return "deletion of the current branch prohibited"; + } + } } + if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && !is_null_sha1(old_sha1) && !prefixcmp(name, "refs/heads/")) { @@ -349,6 +424,7 @@ static void run_update_post_hook(struct command *cmd) static void execute_commands(const char *unpacker_error) { struct command *cmd = commands; + unsigned char sha1[20]; if (unpacker_error) { while (cmd) { @@ -366,6 +442,8 @@ static void execute_commands(const char *unpacker_error) return; } + head_name = resolve_ref("HEAD", sha1, 0, NULL); + while (cmd) { cmd->error_string = update(cmd); cmd = cmd->next; diff --git a/builtin-remote.c b/builtin-remote.c index abc8dd8389..ac69d37c8a 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -298,7 +298,7 @@ static int add_known_remote(struct remote *remote, void *cb_data) struct branches_for_remote { struct remote *remote; - struct string_list *branches; + struct string_list *branches, *skipped; struct known_remotes *keep; }; @@ -323,6 +323,16 @@ static int add_branch_for_removal(const char *refname, return 0; } + /* don't delete non-remote refs */ + if (prefixcmp(refname, "refs/remotes")) { + /* advise user how to delete local branches */ + if (!prefixcmp(refname, "refs/heads/")) + string_list_append(abbrev_branch(refname), + branches->skipped); + /* silently skip over other non-remote refs */ + return 0; + } + /* make sure that symrefs are deleted */ if (flags & REF_ISSYMREF) return unlink(git_path("%s", refname)); @@ -542,8 +552,11 @@ static int rm(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; struct known_remotes known_remotes = { NULL, NULL }; struct string_list branches = { NULL, 0, 0, 1 }; - struct branches_for_remote cb_data = { NULL, &branches, &known_remotes }; - int i; + struct string_list skipped = { NULL, 0, 0, 1 }; + struct branches_for_remote cb_data = { + NULL, &branches, &skipped, &known_remotes + }; + int i, result; if (argc != 2) usage_with_options(builtin_remote_usage, options); @@ -583,14 +596,26 @@ static int rm(int argc, const char **argv) * refs, which are invalidated when deleting a branch. */ cb_data.remote = remote; - i = for_each_ref(add_branch_for_removal, &cb_data); + result = for_each_ref(add_branch_for_removal, &cb_data); strbuf_release(&buf); - if (!i) - i = remove_branches(&branches); + if (!result) + result = remove_branches(&branches); string_list_clear(&branches, 1); - return i; + if (skipped.nr) { + fprintf(stderr, skipped.nr == 1 ? + "Note: A non-remote branch was not removed; " + "to delete it, use:\n" : + "Note: Non-remote branches were not removed; " + "to delete them, use:\n"); + for (i = 0; i < skipped.nr; i++) + fprintf(stderr, " git branch -d %s\n", + skipped.items[i].string); + } + string_list_clear(&skipped, 0); + + return result; } static void show_list(const char *title, struct string_list *list, @@ -731,12 +756,17 @@ static int prune(int argc, const char **argv) OPT_END() }; struct ref_states states; + const char *dangling_msg; argc = parse_options(argc, argv, options, builtin_remote_usage, 0); if (argc < 1) usage_with_options(builtin_remote_usage, options); + dangling_msg = (dry_run + ? " %s will become dangling!\n" + : " %s has become dangling!\n"); + memset(&states, 0, sizeof(states)); for (; argc; argc--, argv++) { int i; @@ -759,6 +789,7 @@ static int prune(int argc, const char **argv) printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); + warn_dangling_symref(dangling_msg, refname); } /* NEEDSWORK: free remote */ diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 857742a14f..436afa45f5 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -608,6 +608,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--bisect-all")) { bisect_list = 1; bisect_find_all = 1; + revs.show_decorations = 1; continue; } if (!strcmp(arg, "--bisect-vars")) { diff --git a/builtin-revert.c b/builtin-revert.c index d48313c745..d210150671 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -223,17 +223,6 @@ static char *help_msg(const unsigned char *sha1) return helpbuf; } -static int index_is_dirty(void) -{ - struct rev_info rev; - init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, "HEAD"); - DIFF_OPT_SET(&rev.diffopt, QUIET); - DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); - run_diff_index(&rev, 1); - return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); -} - static struct tree *empty_tree(void) { struct tree *tree = xcalloc(1, sizeof(struct tree)); @@ -279,7 +268,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) } else { if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); - if (index_is_dirty()) + if (index_differs_from("HEAD", 0)) die ("Dirty index: cannot %s", me); } discard_cache(); diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index bfc78bb3f6..6ae6bcc0e8 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -44,6 +44,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[0], quiet); break; case 2: + if (!strcmp(argv[0], "HEAD") && + prefixcmp(argv[1], "refs/")) + die("Refusing to point HEAD outside of refs/"); create_symref(argv[0], argv[1], msg); break; default: @@ -624,7 +624,7 @@ int is_directory(const char *); const char *make_absolute_path(const char *path); const char *make_nonrelative_path(const char *path); const char *make_relative_path(const char *abs, const char *base); -int normalize_absolute_path(char *buf, const char *path); +int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ @@ -827,6 +827,7 @@ extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); extern void free_pack_by_name(const char *); +extern void clear_delta_base_cache(void); extern struct packed_git *add_packed_git(const char *, int, int); extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t); extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); diff --git a/config.mak.in b/config.mak.in index 55b25c3d26..7cce0c12d5 100644 --- a/config.mak.in +++ b/config.mak.in @@ -13,9 +13,9 @@ TCLTK_PATH = @TCLTK_PATH@ prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ -gitexecdir = @libexecdir@/git-core/ +gitexecdir = @libexecdir@/git-core datarootdir = @datarootdir@ -template_dir = @datadir@/git-core/templates/ +template_dir = @datadir@/git-core/templates mandir=@mandir@ diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 307bf5d4f9..412d2c0dab 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -34,11 +34,11 @@ # are currently in a git repository. The %s token will be # the name of the current branch. # -# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty -# value, unstaged (*) and staged (+) changes will be shown next -# to the branch name. You can configure this per-repository -# with the bash.showDirtyState variable, which defaults to true -# once GIT_PS1_SHOWDIRTYSTATE is enabled. +# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty +# value, unstaged (*) and staged (+) changes will be shown next +# to the branch name. You can configure this per-repository +# with the bash.showDirtyState variable, which defaults to true +# once GIT_PS1_SHOWDIRTYSTATE is enabled. # # To submit patches: # @@ -125,7 +125,7 @@ __git_ps1 () local w local i - if test -n "$GIT_PS1_SHOWDIRTYSTATE"; then + if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then if test "$(git config --bool bash.showDirtyState)" != "false"; then git diff --no-ext-diff --ignore-submodules \ --quiet --exit-code || w="*" @@ -1196,10 +1196,14 @@ _git_config () __gitcomp "$(__git_merge_strategies)" return ;; - color.branch|color.diff|color.status) + color.branch|color.diff|color.interactive|color.status|color.ui) __gitcomp "always never auto" return ;; + color.pager) + __gitcomp "false true" + return + ;; color.*.*) __gitcomp " normal black red green yellow blue magenta cyan white @@ -1606,7 +1610,7 @@ _git_svn () --follow-parent --authors-file= --repack= --no-metadata --use-svm-props --use-svnsync-props --log-window-size= --no-checkout --quiet - --repack-flags --user-log-author --localtime $remote_opts + --repack-flags --use-log-author --localtime $remote_opts " local init_opts=" --template= --shared= --trunk= --tags= diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile index a48540a92b..24d9312941 100644 --- a/contrib/emacs/Makefile +++ b/contrib/emacs/Makefile @@ -2,7 +2,7 @@ EMACS = emacs -ELC = git.elc vc-git.elc git-blame.elc +ELC = git.elc git-blame.elc INSTALL ?= install INSTALL_ELC = $(INSTALL) -m 644 prefix ?= $(HOME) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 09e8bae3a4..fcbe2d9cf5 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1,6 +1,6 @@ ;;; git.el --- A user interface for git -;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org> +;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org> ;; Version: 1.0 @@ -34,15 +34,21 @@ ;; To start: `M-x git-status' ;; ;; TODO -;; - portability to XEmacs ;; - diff against other branch ;; - renaming files from the status buffer ;; - creating tags ;; - fetch/pull -;; - switching branches ;; - revlist browser ;; - git-show-branch browser -;; - menus +;; + +;;; Compatibility: +;; +;; This file works on GNU Emacs 21 or later. It may work on older +;; versions but this is not guaranteed. +;; +;; It may work on XEmacs 21, provided that you first install the ewoc +;; and log-edit packages. ;; (eval-when-compile (require 'cl)) @@ -222,7 +228,7 @@ the process output as a string, or nil if the git command failed." (with-current-buffer buffer (cd dir) (apply #'call-process-region start end program - nil (list output-buffer nil) nil args)))) + nil (list output-buffer t) nil args)))) (defun git-run-command-buffer (buffer-name &rest args) "Run a git command, sending the output to a buffer named BUFFER-NAME." @@ -239,13 +245,15 @@ the process output as a string, or nil if the git command failed." (defun git-run-command-region (buffer start end env &rest args) "Run a git command with specified buffer region as input." - (unless (eq 0 (if env - (git-run-process-region - buffer start end "env" - (append (git-get-env-strings env) (list "git") args)) + (with-temp-buffer + (if (eq 0 (if env (git-run-process-region - buffer start end "git" args))) - (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))) + buffer start end "env" + (append (git-get-env-strings env) (list "git") args)) + (git-run-process-region buffer start end "git" args))) + (buffer-string) + (display-message-or-buffer (current-buffer)) + nil))) (defun git-run-hook (hook env &rest args) "Run a git hook and display its output if any." @@ -397,6 +405,17 @@ the process output as a string, or nil if the git command failed." (unless newval (push "-d" args)) (apply 'git-call-process-display-error "update-ref" args))) +(defun git-for-each-ref (&rest specs) + "Return a list of refs using git-for-each-ref. +Each entry is a cons of (SHORT-NAME . FULL-NAME)." + (let (refs) + (with-temp-buffer + (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs) + (goto-char (point-min)) + (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t) + (push (cons (match-string 1) (match-string 0)) refs))) + (nreverse refs))) + (defun git-read-tree (tree &optional index-file) "Read a tree into the index file." (let ((process-environment @@ -447,18 +466,16 @@ the process output as a string, or nil if the git command failed." (setq coding-system-for-write buffer-file-coding-system)) (let ((commit (git-get-string-sha1 - (with-output-to-string - (with-current-buffer standard-output - (let ((env `(("GIT_AUTHOR_NAME" . ,author-name) - ("GIT_AUTHOR_EMAIL" . ,author-email) - ("GIT_COMMITTER_NAME" . ,(git-get-committer-name)) - ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email))))) - (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env)) - (apply #'git-run-command-region - buffer log-start log-end env - "commit-tree" tree (nreverse args)))))))) - (and (git-update-ref "HEAD" commit head subject) - commit)))) + (let ((env `(("GIT_AUTHOR_NAME" . ,author-name) + ("GIT_AUTHOR_EMAIL" . ,author-email) + ("GIT_COMMITTER_NAME" . ,(git-get-committer-name)) + ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email))))) + (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env)) + (apply #'git-run-command-region + buffer log-start log-end env + "commit-tree" tree (nreverse args)))))) + (when commit (git-update-ref "HEAD" commit head subject)) + commit))) (defun git-empty-db-p () "Check if the git db is empty (no commit done yet)." @@ -562,29 +579,29 @@ the process output as a string, or nil if the git command failed." (let* ((old-type (lsh (or old-perm 0) -9)) (new-type (lsh (or new-perm 0) -9)) (str (case new-type - (?\100 ;; file + (64 ;; file (case old-type - (?\100 nil) - (?\120 " (type change symlink -> file)") - (?\160 " (type change subproject -> file)"))) - (?\120 ;; symlink + (64 nil) + (80 " (type change symlink -> file)") + (112 " (type change subproject -> file)"))) + (80 ;; symlink (case old-type - (?\100 " (type change file -> symlink)") - (?\160 " (type change subproject -> symlink)") + (64 " (type change file -> symlink)") + (112 " (type change subproject -> symlink)") (t " (symlink)"))) - (?\160 ;; subproject + (112 ;; subproject (case old-type - (?\100 " (type change file -> subproject)") - (?\120 " (type change symlink -> subproject)") + (64 " (type change file -> subproject)") + (80 " (type change symlink -> subproject)") (t " (subproject)"))) - (?\110 nil) ;; directory (internal, not a real git state) - (?\000 ;; deleted or unknown + (72 nil) ;; directory (internal, not a real git state) + (0 ;; deleted or unknown (case old-type - (?\120 " (symlink)") - (?\160 " (subproject)"))) + (80 " (symlink)") + (112 " (subproject)"))) (t (format " (unknown type %o)" new-type))))) (cond (str (propertize str 'face 'git-status-face)) - ((eq new-type ?\110) "/") + ((eq new-type 72) "/") (t "")))) (defun git-rename-as-string (info) @@ -1320,6 +1337,7 @@ Return the list of files that haven't been handled." (log-edit-diff-function . git-log-edit-diff)) buffer) (log-edit 'git-do-commit nil 'git-log-edit-files buffer)) (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords)) + (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$")) (setq buffer-file-coding-system coding-system) (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) @@ -1356,6 +1374,36 @@ Return the list of files that haven't been handled." (push (match-string 1) files))) files)) +(defun git-read-commit-name (prompt &optional default) + "Ask for a commit name, with completion for local branch, remote branch and tag." + (completing-read prompt + (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref))) + nil nil nil nil default)) + +(defun git-checkout (branch &optional merge) + "Checkout a branch, tag, or any commit. +Use a prefix arg if git should merge while checking out." + (interactive + (list (git-read-commit-name "Checkout: ") + current-prefix-arg)) + (unless git-status (error "Not in git-status buffer.")) + (let ((args (list branch "--"))) + (when merge (push "-m" args)) + (when (apply #'git-call-process-display-error "checkout" args) + (git-update-status-files)))) + +(defun git-branch (branch) + "Create a branch from the current HEAD and switch to it." + (interactive (list (git-read-commit-name "Branch: "))) + (unless git-status (error "Not in git-status buffer.")) + (if (git-rev-parse (concat "refs/heads/" branch)) + (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch)) + (and (git-call-process-display-error "branch" "-f" branch) + (git-call-process-display-error "checkout" branch)) + (message "Canceled.")) + (git-call-process-display-error "checkout" "-b" branch)) + (git-refresh-ewoc-hf git-status)) + (defun git-amend-commit () "Undo the last commit on HEAD, and set things up to commit an amended version of it." @@ -1372,6 +1420,44 @@ amended version of it." (git-setup-commit-buffer commit) (git-commit-file)))) +(defun git-cherry-pick-commit (arg) + "Cherry-pick a commit." + (interactive (list (git-read-commit-name "Cherry-pick commit: "))) + (unless git-status (error "Not in git-status buffer.")) + (let ((commit (git-rev-parse (concat arg "^0")))) + (unless commit (error "Not a valid commit '%s'." arg)) + (when (git-rev-parse (concat commit "^2")) + (error "Cannot cherry-pick a merge commit.")) + (let ((files (git-get-commit-files commit)) + (ok (git-call-process-display-error "cherry-pick" "-n" commit))) + (git-update-status-files files ok) + (with-current-buffer (git-setup-commit-buffer commit) + (goto-char (point-min)) + (if (re-search-forward "^\n*Signed-off-by:" nil t 1) + (goto-char (match-beginning 0)) + (goto-char (point-max))) + (insert "(cherry picked from commit " commit ")\n")) + (when ok (git-commit-file))))) + +(defun git-revert-commit (arg) + "Revert a commit." + (interactive (list (git-read-commit-name "Revert commit: "))) + (unless git-status (error "Not in git-status buffer.")) + (let ((commit (git-rev-parse (concat arg "^0")))) + (unless commit (error "Not a valid commit '%s'." arg)) + (when (git-rev-parse (concat commit "^2")) + (error "Cannot revert a merge commit.")) + (let ((files (git-get-commit-files commit)) + (subject (git-get-commit-description commit)) + (ok (git-call-process-display-error "revert" "-n" commit))) + (git-update-status-files files ok) + (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject) + (setq subject (match-string 1 subject))) + (git-setup-log-buffer (get-buffer-create "*git-commit*") + (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil + (format "This reverts commit %s.\n" commit)) + (when ok (git-commit-file))))) + (defun git-find-file () "Visit the current file in its own buffer." (interactive) @@ -1471,6 +1557,10 @@ amended version of it." (define-key map "\M-\C-?" 'git-unmark-all) ; the commit submap (define-key commit-map "\C-a" 'git-amend-commit) + (define-key commit-map "\C-b" 'git-branch) + (define-key commit-map "\C-o" 'git-checkout) + (define-key commit-map "\C-p" 'git-cherry-pick-commit) + (define-key commit-map "\C-v" 'git-revert-commit) ; the diff submap (define-key diff-map "b" 'git-diff-file-base) (define-key diff-map "c" 'git-diff-file-combined) @@ -1491,6 +1581,10 @@ amended version of it." `("Git" ["Refresh" git-refresh-status t] ["Commit" git-commit-file t] + ["Checkout..." git-checkout t] + ["New Branch..." git-branch t] + ["Cherry-pick Commit..." git-cherry-pick-commit t] + ["Revert Commit..." git-revert-commit t] ("Merge" ["Next Unmerged File" git-next-unmerged-file t] ["Prev Unmerged File" git-prev-unmerged-file t] diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el deleted file mode 100644 index b8f6be5c0a..0000000000 --- a/contrib/emacs/vc-git.el +++ /dev/null @@ -1,216 +0,0 @@ -;;; vc-git.el --- VC backend for the git version control system - -;; Copyright (C) 2006 Alexandre Julliard - -;; This program is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License as -;; published by the Free Software Foundation; either version 2 of -;; the License, or (at your option) any later version. -;; -;; This program is distributed in the hope that it will be -;; useful, but WITHOUT ANY WARRANTY; without even the implied -;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -;; PURPOSE. See the GNU General Public License for more details. -;; -;; You should have received a copy of the GNU General Public -;; License along with this program; if not, write to the Free -;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, -;; MA 02111-1307 USA - -;;; Commentary: - -;; This file contains a VC backend for the git version control -;; system. -;; -;; To install: put this file on the load-path and add GIT to the list -;; of supported backends in `vc-handled-backends'; the following line, -;; placed in your ~/.emacs, will accomplish this: -;; -;; (add-to-list 'vc-handled-backends 'GIT) -;; -;; TODO -;; - changelog generation -;; - working with revisions other than HEAD -;; - -(eval-when-compile (require 'cl)) - -(defvar git-commits-coding-system 'utf-8 - "Default coding system for git commits.") - -(defun vc-git--run-command-string (file &rest args) - "Run a git command on FILE and return its output as string." - (let* ((ok t) - (str (with-output-to-string - (with-current-buffer standard-output - (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil - (append args (list (file-relative-name file))))) - (setq ok nil)))))) - (and ok str))) - -(defun vc-git--run-command (file &rest args) - "Run a git command on FILE, discarding any output." - (let ((name (file-relative-name file))) - (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name)))))) - -(defun vc-git-registered (file) - "Check whether FILE is registered with git." - (with-temp-buffer - (let* ((dir (file-name-directory file)) - (name (file-relative-name file dir))) - (and (ignore-errors - (when dir (cd dir)) - (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) - (let ((str (buffer-string))) - (and (> (length str) (length name)) - (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) - -(defun vc-git-state (file) - "git-specific version of `vc-state'." - (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--"))) - (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff)) - 'edited - 'up-to-date))) - -(defun vc-git-workfile-version (file) - "git-specific version of `vc-workfile-version'." - (let ((str (with-output-to-string - (with-current-buffer standard-output - (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD"))))) - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (match-string 2 str) - str))) - -(defun vc-git-symbolic-commit (commit) - "Translate COMMIT string into symbolic form. -Returns nil if not possible." - (and commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "name-rev" - "--name-only" "--tags" - commit)) - (goto-char (point-min)) - (= (forward-line 2) 1) - (bolp) - (buffer-substring-no-properties (point-min) (1- (point-max))))))) - -(defun vc-git-previous-version (file rev) - "git-specific version of `vc-previous-version'." - (let ((default-directory (file-name-directory (expand-file-name file))) - (file (file-name-nondirectory file))) - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-2" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (not (bobp)) - (buffer-substring-no-properties - (point) - (1- (point-max)))))))) - -(defun vc-git-next-version (file rev) - "git-specific version of `vc-next-version'." - (let* ((default-directory (file-name-directory - (expand-file-name file))) - (file (file-name-nondirectory file)) - (current-rev - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-1" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (bobp) - (buffer-substring-no-properties - (point) - (1- (point-max))))))) - (and current-rev - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "HEAD" "--" file)) - (goto-char (point-min)) - (search-forward current-rev nil t) - (zerop (forward-line -1)) - (buffer-substring-no-properties - (point) - (progn (forward-line 1) (1- (point)))))))))) - -(defun vc-git-revert (file &optional contents-done) - "Revert FILE to the version stored in the git repository." - (if contents-done - (vc-git--run-command file "update-index" "--") - (vc-git--run-command file "checkout" "HEAD"))) - -(defun vc-git-checkout-model (file) - 'implicit) - -(defun vc-git-workfile-unchanged-p (file) - (let ((sha1 (vc-git--run-command-string file "hash-object" "--")) - (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--"))) - (and head - (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head) - (string= (car (split-string sha1 "\n")) (match-string 1 head))))) - -(defun vc-git-register (file &optional rev comment) - "Register FILE into the git version-control system." - (vc-git--run-command file "update-index" "--add" "--")) - -(defun vc-git-print-log (file &optional buffer) - (let ((name (file-relative-name file)) - (coding-system-for-read git-commits-coding-system)) - (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--"))) - -(defun vc-git-diff (file &optional rev1 rev2 buffer) - (let ((name (file-relative-name file)) - (buf (or buffer "*vc-diff*"))) - (if (and rev1 rev2) - (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--") - (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--")) - ; git-diff-index doesn't set exit status like diff does - (if (vc-git-workfile-unchanged-p file) 0 1))) - -(defun vc-git-checkin (file rev comment) - (let ((coding-system-for-write git-commits-coding-system)) - (vc-git--run-command file "commit" "-m" comment "--only" "--"))) - -(defun vc-git-checkout (file &optional editable rev destfile) - (if destfile - (let ((fullname (substring - (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--") - 0 -1)) - (coding-system-for-read 'no-conversion) - (coding-system-for-write 'no-conversion)) - (with-temp-file destfile - (eq 0 (call-process "git" nil t nil "cat-file" "blob" - (concat (or rev "HEAD") ":" fullname))))) - (vc-git--run-command file "checkout" (or rev "HEAD")))) - -(defun vc-git-annotate-command (file buf &optional rev) - ; FIXME: rev is ignored - (let ((name (file-relative-name file))) - (call-process "git" nil buf nil "blame" name))) - -(defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t) - (vc-annotate-convert-time - (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7)))))) - -;; Not really useful since we can't do anything with the revision yet -;;(defun vc-annotate-extract-revision-at-line () -;; (save-excursion -;; (move-beginning-of-line 1) -;; (and (looking-at "[0-9a-f]+") -;; (buffer-substring (match-beginning 0) (match-end 0))))) - -(provide 'vc-git) diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 28a3c0e46e..60cbab65d3 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -615,7 +615,9 @@ show_new_revisions() revspec=$oldrev..$newrev fi - git rev-parse --not --branches | grep -v $(git rev-parse $refname) | + other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ | + grep -F -v $refname) + git rev-parse --not $other_branches | if [ -z "$custom_showrev" ] then git rev-list --pretty --stdin $revspec diff --git a/diff-lib.c b/diff-lib.c index a41e1ec07c..79d0606834 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -513,3 +513,18 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) exit(128); return 0; } + +int index_differs_from(const char *def, int diff_flags) +{ + struct rev_info rev; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, def); + DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); + rev.diffopt.flags |= diff_flags; + run_diff_index(&rev, 1); + if (rev.pending.alloc) + free(rev.pending.objects); + return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0); +} @@ -184,11 +184,11 @@ static int remove_tempfile_installed; static void remove_tempfile(void) { int i; - for (i = 0; i < ARRAY_SIZE(diff_temp); i++) - if (diff_temp[i].name == diff_temp[i].tmp_path) { + for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { + if (diff_temp[i].name == diff_temp[i].tmp_path) unlink(diff_temp[i].name); - diff_temp[i].name = NULL; - } + diff_temp[i].name = NULL; + } } static void remove_tempfile_on_signal(int signo) @@ -2326,15 +2326,12 @@ void diff_setup(struct diff_options *options) options->break_opt = -1; options->rename_limit = -1; options->dirstat_percent = 3; - DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); options->context = 3; options->change = diff_change; options->add_remove = diff_addremove; if (diff_use_color_default > 0) DIFF_OPT_SET(options, COLOR_DIFF); - else - DIFF_OPT_CLR(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; if (!diff_mnemonic_prefix) { @@ -265,4 +265,6 @@ extern int diff_result_code(struct diff_options *, int); extern void diff_no_index(struct rev_info *, int, const char **, int, const char *); +extern int index_differs_from(const char *def, int diff_flags); + #endif /* DIFF_H */ diff --git a/fast-import.c b/fast-import.c index 1935206be0..3ef3413e69 100644 --- a/fast-import.c +++ b/fast-import.c @@ -869,7 +869,7 @@ static char *create_index(void) /* Generate the fan-out array. */ c = idx; for (i = 0; i < 256; i++) { - struct object_entry **next = c;; + struct object_entry **next = c; while (next < last) { if ((*next)->sha1[0] != i) break; @@ -945,6 +945,7 @@ static void end_packfile(void) { struct packed_git *old_p = pack_data, *new_p; + clear_delta_base_cache(); if (object_count) { char *idx_name; int i; diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 3bf0cda4ee..5f129a4203 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -12,6 +12,13 @@ my ($prompt_color, $header_color, $help_color) = $repo->get_color('color.interactive.header', 'bold'), $repo->get_color('color.interactive.help', 'red bold'), ) : (); +my $error_color = (); +if ($menu_use_color) { + my $help_color_spec = ($repo->config('color.interactive.help') or + 'red bold'); + $error_color = $repo->get_color('color.interactive.error', + $help_color_spec); +} my $diff_use_color = $repo->get_colorbool('color.diff'); my ($fraginfo_color) = @@ -33,6 +40,17 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); +my $use_readkey = 0; +sub ReadMode; +sub ReadKey; +if ($repo->config_bool("interactive.singlekey")) { + eval { + require Term::ReadKey; + Term::ReadKey->import; + $use_readkey = 1; + }; +} + sub colored { my $color = shift; my $string = join("", @_); @@ -325,6 +343,10 @@ sub highlight_prefix { return "$prompt_color$prefix$normal_color$remainder"; } +sub error_msg { + print STDERR colored $error_color, @_; +} + sub list_and_choose { my ($opts, @stuff) = @_; my (@chosen, @return); @@ -420,12 +442,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -758,11 +780,32 @@ sub diff_applies { return close $fh; } +sub _restore_terminal_and_die { + ReadMode 'restore'; + print "\n"; + exit 1; +} + +sub prompt_single_character { + if ($use_readkey) { + local $SIG{TERM} = \&_restore_terminal_and_die; + local $SIG{INT} = \&_restore_terminal_and_die; + ReadMode 'cbreak'; + my $key = ReadKey 0; + ReadMode 'restore'; + print "$key" if defined $key; + print "\n"; + return $key; + } else { + return <STDIN>; + } +} + sub prompt_yesno { my ($prompt) = @_; while (1) { print colored $prompt_color, $prompt; - my $line = <STDIN>; + my $line = prompt_single_character; return 0 if $line =~ /^n/i; return 1 if $line =~ /^y/i; } @@ -893,7 +936,7 @@ sub patch_update_file { print @{$mode->{DISPLAY}}; print colored $prompt_color, "Stage mode change [y/n/a/d/?]? "; - my $line = <STDIN>; + my $line = prompt_single_character; if ($line =~ /^y/i) { $mode->{USE} = 1; last; @@ -966,7 +1009,7 @@ sub patch_update_file { print; } print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? "; - my $line = <STDIN>; + my $line = prompt_single_character; if ($line) { if ($line =~ /^y/i) { $hunk[$ix]{USE} = 1; @@ -1000,11 +1043,11 @@ sub patch_update_file { chomp $response; } if ($response !~ /^\s*\d+\s*$/) { - print STDERR "Invalid number: '$response'\n"; + error_msg "Invalid number: '$response'\n"; } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - print STDERR "Sorry, only $num hunks available.\n"; + error_msg "Sorry, only $num hunks available.\n"; } next; } @@ -1018,14 +1061,22 @@ sub patch_update_file { next; } elsif ($line =~ m|^/(.*)|) { + my $regex = $1; + if ($1 eq "") { + print colored $prompt_color, "search for regex? "; + $regex = <STDIN>; + if (defined $regex) { + chomp $regex; + } + } my $search_string; eval { - $search_string = qr{$1}m; + $search_string = qr{$regex}m; }; if ($@) { my ($err,$exp) = ($@, $1); $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; - print STDERR "Malformed search regexp $exp: $err\n"; + error_msg "Malformed search regexp $exp: $err\n"; next; } my $iy = $ix; @@ -1035,7 +1086,7 @@ sub patch_update_file { $iy++; $iy = 0 if ($iy >= $num); if ($ix == $iy) { - print STDERR "No hunk matches the given pattern\n"; + error_msg "No hunk matches the given pattern\n"; last; } } @@ -1047,7 +1098,7 @@ sub patch_update_file { $ix--; } else { - print STDERR "No previous hunk\n"; + error_msg "No previous hunk\n"; } next; } @@ -1056,7 +1107,7 @@ sub patch_update_file { $ix++; } else { - print STDERR "No next hunk\n"; + error_msg "No next hunk\n"; } next; } @@ -1069,13 +1120,13 @@ sub patch_update_file { } } else { - print STDERR "No previous hunk\n"; + error_msg "No previous hunk\n"; } next; } elsif ($line =~ /^j/) { if ($other !~ /j/) { - print STDERR "No next hunk\n"; + error_msg "No next hunk\n"; next; } } diff --git a/git-filter-branch.sh b/git-filter-branch.sh index eb62f719b0..27b57b826a 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -108,7 +108,7 @@ OPTIONS_SPEC= . git-sh-setup if [ "$(is_bare_repository)" = false ]; then - git diff-files --quiet && + git diff-files --ignore-submodules --quiet && git diff-index --cached --quiet HEAD -- || die "Cannot rewrite branch(es) with a dirty working directory." fi @@ -221,7 +221,7 @@ die "" trap 'cd ../..; rm -rf "$tempdir"' 0 # Make sure refs/original is empty -git for-each-ref > "$tempdir"/backup-refs +git for-each-ref > "$tempdir"/backup-refs || exit while read sha1 type name do case "$force,$name" in @@ -241,8 +241,9 @@ GIT_WORK_TREE=. export GIT_DIR GIT_WORK_TREE # The refs should be updated if their heads were rewritten -git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" | -sed -e '/^^/d' >"$tempdir"/heads +git rev-parse --no-flags --revs-only --symbolic-full-name \ + --default HEAD "$@" > "$tempdir"/raw-heads || exit +sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads test -s "$tempdir"/heads || die "Which ref do you want to rewrite?" @@ -251,8 +252,6 @@ GIT_INDEX_FILE="$(pwd)/../index" export GIT_INDEX_FILE git read-tree || die "Could not seed the index" -ret=0 - # map old->new commit ids for rewriting parents mkdir ../map || die "Could not create map/ directory" @@ -315,10 +314,11 @@ while read commit parents; do die "tree filter failed: $filter_tree" ( - git diff-index -r --name-only $commit + git diff-index -r --name-only $commit && git ls-files --others - ) | - git update-index --add --replace --remove --stdin + ) > "$tempdir"/tree-state || exit + git update-index --add --replace --remove --stdin \ + < "$tempdir"/tree-state || exit fi eval "$filter_index" < /dev/null || @@ -339,7 +339,8 @@ while read commit parents; do eval "$filter_msg" > ../message || die "msg filter failed: $filter_msg" @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \ - $(git write-tree) $parentstr < ../message > ../map/$commit + $(git write-tree) $parentstr < ../message > ../map/$commit || + die "could not write rewritten commit" done <../revs # In case of a subdirectory filter, it is possible that a specified head @@ -407,7 +408,8 @@ do die "Could not rewrite $ref" ;; esac - git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 + git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 || + exit done < "$tempdir"/heads # TODO: This should possibly go, with the semantics that all positive given @@ -469,20 +471,21 @@ rm -rf "$tempdir" trap - 0 +unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE +test -z "$ORIG_GIT_DIR" || { + GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR +} +test -z "$ORIG_GIT_WORK_TREE" || { + GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" && + export GIT_WORK_TREE +} +test -z "$ORIG_GIT_INDEX_FILE" || { + GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" && + export GIT_INDEX_FILE +} + if [ "$(is_bare_repository)" = false ]; then - unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE - test -z "$ORIG_GIT_DIR" || { - GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR - } - test -z "$ORIG_GIT_WORK_TREE" || { - GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" && - export GIT_WORK_TREE - } - test -z "$ORIG_GIT_INDEX_FILE" || { - GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" && - export GIT_INDEX_FILE - } - git read-tree -u -m HEAD + git read-tree -u -m HEAD || exit fi -exit $ret +exit 0 diff --git a/git-mergetool.sh b/git-mergetool.sh index 09f3a1068f..87fa88af55 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -13,7 +13,6 @@ SUBDIRECTORY_OK=Yes OPTIONS_SPEC= . git-sh-setup require_work_tree -prefix=$(git rev-parse --show-prefix) # Returns true if the mode reflects a symlink is_symlink () { @@ -127,6 +126,14 @@ check_unchanged () { fi } +checkout_staged_file () { + tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ') + + if test $? -eq 0 -a -n "$tmpfile" ; then + mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3" + fi +} + merge_file () { MERGED="$1" @@ -153,9 +160,9 @@ merge_file () { local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'` remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'` - base_present && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null - local_present && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null - remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null + base_present && checkout_staged_file 1 "$MERGED" "$BASE" + local_present && checkout_staged_file 2 "$MERGED" "$LOCAL" + remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE" if test -z "$local_mode" -o -z "$remote_mode"; then echo "Deleted merge conflict for '$MERGED':" diff --git a/git-repack.sh b/git-repack.sh index 458a497af8..be6db5e805 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -88,32 +88,79 @@ if [ -z "$names" ]; then echo Nothing new to pack. fi fi -for name in $names ; do - fullbases="$fullbases pack-$name" - chmod a-w "$PACKTMP-$name.pack" - chmod a-w "$PACKTMP-$name.idx" - mkdir -p "$PACKDIR" || exit +# Ok we have prepared all new packfiles. +mkdir -p "$PACKDIR" || exit + +# First see if there are packs of the same name and if so +# if we can move them out of the way (this can happen if we +# repacked immediately after packing fully. +rollback= +failed= +for name in $names +do for sfx in pack idx do - if test -f "$PACKDIR/pack-$name.$sfx" - then - mv -f "$PACKDIR/pack-$name.$sfx" \ - "$PACKDIR/old-pack-$name.$sfx" - fi - done && + file=pack-$name.$sfx + test -f "$PACKDIR/$file" || continue + rm -f "$PACKDIR/old-$file" && + mv "$PACKDIR/$file" "$PACKDIR/old-$file" || { + failed=t + break + } + rollback="$rollback $file" + done + test -z "$failed" || break +done + +# If renaming failed for any of them, roll the ones we have +# already renamed back to their original names. +if test -n "$failed" +then + rollback_failure= + for file in $rollback + do + mv "$PACKDIR/old-$file" "$PACKDIR/$file" || + rollback_failure="$rollback_failure $file" + done + if test -n "$rollback_failure" + then + echo >&2 "WARNING: Some packs in use have been renamed by" + echo >&2 "WARNING: prefixing old- to their name, in order to" + echo >&2 "WARNING: replace them with the new version of the" + echo >&2 "WARNING: file. But the operation failed, and" + echo >&2 "WARNING: attempt to rename them back to their" + echo >&2 "WARNING: original names also failed." + echo >&2 "WARNING: Please rename them in $PACKDIR manually:" + for file in $rollback_failure + do + echo >&2 "WARNING: old-$file -> $file" + done + fi + exit 1 +fi + +# Now the ones with the same name are out of the way... +fullbases= +for name in $names +do + fullbases="$fullbases pack-$name" + chmod a-w "$PACKTMP-$name.pack" + chmod a-w "$PACKTMP-$name.idx" mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" && - mv -f "$PACKTMP-$name.idx" "$PACKDIR/pack-$name.idx" && - test -f "$PACKDIR/pack-$name.pack" && - test -f "$PACKDIR/pack-$name.idx" || { - echo >&2 "Couldn't replace the existing pack with updated one." - echo >&2 "The original set of packs have been saved as" - echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR." - exit 1 - } - rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx" + mv -f "$PACKTMP-$name.idx" "$PACKDIR/pack-$name.idx" || + exit +done + +# Remove the "old-" files +for name in $names +do + rm -f "$PACKDIR/old-pack-$name.idx" + rm -f "$PACKDIR/old-pack-$name.pack" done +# End of pack replacement. + if test "$remove_redundant" = t then # We know $existing are all redundant. diff --git a/git-submodule.sh b/git-submodule.sh index 2f47e065fe..6cc2d334c0 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -59,7 +59,7 @@ resolve_relative_url () # module_list() { - git ls-files --stage -- "$@" | grep '^160000 ' + git ls-files --error-unmatch --stage -- "$@" | grep '^160000 ' } # diff --git a/git-svn.perl b/git-svn.perl index 79888a05c4..001a1d8eff 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2389,22 +2389,8 @@ sub find_parent_branch { print STDERR "Found possible branch point: ", "$new_url => ", $self->full_url, ", $r\n"; $branch_from =~ s#^/##; - my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); - unless ($gs) { - my $ref_id = $self->{ref_id}; - $ref_id =~ s/\@\d+$//; - $ref_id .= "\@$r"; - # just grow a tail if we're not unique enough :x - $ref_id .= '-' while find_ref($ref_id); - print STDERR "Initializing parent: $ref_id\n"; - my ($u, $p, $repo_id) = ($new_url, '', $ref_id); - if ($u =~ s#^\Q$url\E(/|$)##) { - $p = $u; - $u = $url; - $repo_id = $self->{repo_id}; - } - $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); - } + my $gs = $self->other_gs($new_url, $url, $repos_root, + $branch_from, $r, $self->{ref_id}); my ($r0, $parent) = $gs->find_rev_before($r, 1); { my ($base, $head); @@ -2431,7 +2417,7 @@ sub find_parent_branch { # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it $self->{last_commit} = $parent; - $ed = SVN::Git::Fetcher->new($self); + $ed = SVN::Git::Fetcher->new($self, $gs->{path}); $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; @@ -2586,6 +2572,28 @@ sub parse_svn_date { return $parsed_date; } +sub other_gs { + my ($self, $new_url, $url, $repos_root, + $branch_from, $r, $old_ref_id) = @_; + my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); + unless ($gs) { + my $ref_id = $old_ref_id; + $ref_id =~ s/\@\d+$//; + $ref_id .= "\@$r"; + # just grow a tail if we're not unique enough :x + $ref_id .= '-' while find_ref($ref_id); + print STDERR "Initializing parent: $ref_id\n"; + my ($u, $p, $repo_id) = ($new_url, '', $ref_id); + if ($u =~ s#^\Q$url\E(/|$)##) { + $p = $u; + $u = $url; + $repo_id = $self->{repo_id}; + } + $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); + } + $gs +} + sub check_author { my ($author) = @_; if (!defined $author || length $author == 0) { @@ -3250,12 +3258,13 @@ use vars qw/$_ignore_regex/; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { - my ($class, $git_svn) = @_; + my ($class, $git_svn, $switch_path) = @_; my $self = SVN::Delta::Editor->new; bless $self, $class; if (exists $git_svn->{last_commit}) { $self->{c} = $git_svn->{last_commit}; - $self->{empty_symlinks} = _mark_empty_symlinks($git_svn); + $self->{empty_symlinks} = + _mark_empty_symlinks($git_svn, $switch_path); } $self->{empty} = {}; $self->{dir_prop} = {}; @@ -3270,19 +3279,39 @@ sub new { # not inside them (when the Git::SVN::Fetcher object is passed) to # do_{switch,update} sub _mark_empty_symlinks { - my ($git_svn) = @_; + my ($git_svn, $switch_path) = @_; + my $bool = Git::config_bool('svn.brokenSymlinkWorkaround'); + return {} if (defined($bool) && ! $bool); + my %ret; my ($rev, $cmt) = $git_svn->last_rev_commit; return {} unless ($rev && $cmt); + # allow the warning to be printed for each revision we fetch to + # ensure the user sees it. The user can also disable the workaround + # on the repository even while git svn is running and the next + # revision fetched will skip this expensive function. + my $printed_warning; chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`); my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt); local $/ = "\0"; - my $pfx = $git_svn->{path}; + my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path}; $pfx .= '/' if length($pfx); while (<$ls>) { chomp; s/\A100644 blob $empty_blob\t//o or next; + unless ($printed_warning) { + print STDERR "Scanning for empty symlinks, ", + "this may take a while if you have ", + "many empty files\n", + "You may disable this with `", + "git config svn.brokenSymlinkWorkaround ", + "false'.\n", + "This may be done in a different ", + "terminal without restarting ", + "git svn\n"; + $printed_warning = 1; + } my $path = $_; my (undef, $props) = $git_svn->ra->get_file($pfx.$path, $rev, undef); @@ -4348,6 +4377,9 @@ sub gs_fetch_loop_common { } $self->get_log([$longest_path], $min, $max, 0, 1, 1, sub { $revs{$_[1]} = _cb(@_) }); + if ($err) { + print "Checked through r$max\r"; + } if ($err && $max >= $head) { print STDERR "Path '$longest_path' ", "was probably deleted:\n", diff --git a/git-web--browse.sh b/git-web--browse.sh index 78d236b77f..7ed0faddcd 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -115,7 +115,7 @@ if test -z "$browser" ; then browser_candidates="open $browser_candidates" fi # /bin/start indicates MinGW - if test -n /bin/start; then + if test -x /bin/start; then browser_candidates="start $browser_candidates" fi diff --git a/git.spec.in b/git.spec.in index 069ace050d..4be0834f0b 100644 --- a/git.spec.in +++ b/git.spec.in @@ -97,7 +97,7 @@ BuildRequires: perl(Error) %description -n perl-Git Perl interface to Git -%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-core-%{version} +%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version} %prep %setup -q @@ -190,6 +190,9 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com> +- fixed broken git help -w after renaming the git-core package to git. + * Fri Sep 12 2008 Quy Tonthat <qtonthat@gmail.com> - move git-cvsserver to bindir. diff --git a/gitweb/README b/gitweb/README index a9dc2e57d9..8433dd1d45 100644 --- a/gitweb/README +++ b/gitweb/README @@ -212,6 +212,11 @@ not include variables usually directly set during build): Rename detection options for git-diff and git-diff-tree. By default ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or set it to () if you don't want to have renames detection. + * $prevent_xss + If true, some gitweb features are disabled to prevent content in + repositories from launching cross-site scripting (XSS) attacks. Set this + to true if you don't trust the content of your repositories. The default + is false. Projects list file format @@ -258,7 +263,9 @@ You can use the following files in repository: A .html file (HTML fragment) which is included on the gitweb project summary page inside <div> block element. You can use it for longer description of a project, to provide links (for example to project's - homepage), etc. + homepage), etc. This is recognized only if XSS prevention is off + ($prevent_xss is false); a way to include a readme safely when XSS + prevention is on may be worked out in the future. * description (or gitweb.description) Short (shortened by default to 25 characters in the projects list page) single line description of a project (of a repository). Plain text file; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f27dbb6bf4..8dffa3fd53 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -132,6 +132,10 @@ our $fallback_encoding = 'latin1'; # - one might want to include '-B' option, e.g. '-B', '-M' our @diff_opts = ('-M'); # taken from git_commit +# Disables features that would allow repository owners to inject script into +# the gitweb domain. +our $prevent_xss = 0; + # information about snapshot formats that gitweb is capable of serving our %known_snapshot_formats = ( # name => { @@ -1364,7 +1368,7 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{8,40})/) { + if ($line =~ m/\b([0-9a-fA-F]{8,40})\b/) { my $hash_text = $1; my $link = $cgi->a({-href => href(action=>"object", hash=>$hash_text), @@ -4503,7 +4507,9 @@ sub git_summary { print "</table>\n"; - if (-s "$projectroot/$project/README.html") { + # If XSS prevention is on, we don't include README.html. + # TODO: Allow a readme in some safe format. + if (!$prevent_xss && -s "$projectroot/$project/README.html") { print "<div class=\"title\">readme</div>\n" . "<div class=\"readme\">\n"; insert_file("$projectroot/$project/README.html"); @@ -4764,10 +4770,21 @@ sub git_blob_plain { $save_as .= '.txt'; } + # With XSS prevention on, blobs of all types except a few known safe + # ones are served with "Content-Disposition: attachment" to make sure + # they don't run in our security domain. For certain image types, + # blob view writes an <img> tag referring to blob_plain view, and we + # want to be sure not to break that by serving the image as an + # attachment (though Firefox 3 doesn't seem to care). + my $sandbox = $prevent_xss && + $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!; + print $cgi->header( -type => $type, -expires => $expires, - -content_disposition => 'inline; filename="' . $save_as . '"'); + -content_disposition => + ($sandbox ? 'attachment' : 'inline') + . '; filename="' . $save_as . '"'); undef $/; binmode STDOUT, ':raw'; print <$fd>; diff --git a/http-push.c b/http-push.c index 18d81acd1b..30d2d34041 100644 --- a/http-push.c +++ b/http-push.c @@ -153,6 +153,7 @@ struct remote_lock char *url; char *owner; char *token; + char tmpfile_suffix[41]; time_t start_time; long timeout; int refreshing; @@ -210,6 +211,15 @@ static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum d return dav_headers; } +static void append_remote_object_url(struct strbuf *buf, const char *url, + const char *hex, + int only_two_digit_prefix) +{ + strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex); + if (!only_two_digit_prefix) + strbuf_addf(buf, "%s", hex+2); +} + static void finish_request(struct transfer_request *request); static void release_request(struct transfer_request *request); @@ -222,6 +232,15 @@ static void process_response(void *callback_data) } #ifdef USE_CURL_MULTI + +static char *get_remote_object_url(const char *url, const char *hex, + int only_two_digit_prefix) +{ + struct strbuf buf = STRBUF_INIT; + append_remote_object_url(&buf, url, hex, only_two_digit_prefix); + return strbuf_detach(&buf, NULL); +} + static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) { @@ -256,7 +275,6 @@ static void start_fetch_loose(struct transfer_request *request) char *filename; char prevfile[PATH_MAX]; char *url; - char *posn; int prevlocal; unsigned char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; @@ -306,17 +324,8 @@ static void start_fetch_loose(struct transfer_request *request) git_SHA1_Init(&request->c); - url = xmalloc(strlen(remote->url) + 50); - request->url = xmalloc(strlen(remote->url) + 50); - strcpy(url, remote->url); - posn = url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - *(posn++) = '/'; - strcpy(posn, hex + 2); - strcpy(request->url, url); + url = get_remote_object_url(remote->url, hex, 0); + request->url = xstrdup(url); /* If a previous temp file is present, process what was already fetched. */ @@ -389,16 +398,8 @@ static void start_mkcol(struct transfer_request *request) { char *hex = sha1_to_hex(request->obj->sha1); struct active_request_slot *slot; - char *posn; - request->url = xmalloc(strlen(remote->url) + 13); - strcpy(request->url, remote->url); - posn = request->url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - strcpy(posn, "/"); + request->url = get_remote_object_url(remote->url, hex, 1); slot = get_active_slot(); slot->callback_func = process_response; @@ -513,7 +514,7 @@ static void start_put(struct transfer_request *request) { char *hex = sha1_to_hex(request->obj->sha1); struct active_request_slot *slot; - char *posn; + struct strbuf buf = STRBUF_INIT; enum object_type type; char hdr[50]; void *unpacked; @@ -552,21 +553,13 @@ static void start_put(struct transfer_request *request) request->buffer.buf.len = stream.total_out; - request->url = xmalloc(strlen(remote->url) + - strlen(request->lock->token) + 51); - strcpy(request->url, remote->url); - posn = request->url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - *(posn++) = '/'; - strcpy(posn, hex + 2); - request->dest = xmalloc(strlen(request->url) + 14); - sprintf(request->dest, "Destination: %s", request->url); - posn += 38; - *(posn++) = '_'; - strcpy(posn, request->lock->token); + strbuf_addstr(&buf, "Destination: "); + append_remote_object_url(&buf, remote->url, hex, 0); + request->dest = strbuf_detach(&buf, NULL); + + append_remote_object_url(&buf, remote->url, hex, 0); + strbuf_add(&buf, request->lock->tmpfile_suffix, 41); + request->url = strbuf_detach(&buf, NULL); slot = get_active_slot(); slot->callback_func = process_response; @@ -1137,6 +1130,8 @@ static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed) static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) { struct remote_lock *lock = (struct remote_lock *)ctx->userData; + git_SHA_CTX sha_ctx; + unsigned char lock_token_sha1[20]; if (tag_closed && ctx->cdata) { if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) { @@ -1149,6 +1144,13 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { lock->token = xmalloc(strlen(ctx->cdata) + 1); strcpy(lock->token, ctx->cdata); + + git_SHA1_Init(&sha_ctx); + git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token)); + git_SHA1_Final(lock_token_sha1, &sha_ctx); + + lock->tmpfile_suffix[0] = '_'; + memcpy(lock->tmpfile_suffix + 1, sha1_to_hex(lock_token_sha1), 40); } } } diff --git a/log-tree.c b/log-tree.c index 194ddb13da..84a74e544b 100644 --- a/log-tree.c +++ b/log-tree.c @@ -48,7 +48,7 @@ static void show_parents(struct commit *commit, int abbrev) struct commit_list *p; for (p = commit->parents; p ; p = p->next) { struct commit *parent = p->item; - printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev)); + printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev)); } } @@ -280,7 +280,7 @@ void show_log(struct rev_info *opt) putchar('>'); } } - fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); + fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); show_decorations(opt, commit); @@ -348,13 +348,13 @@ void show_log(struct rev_info *opt) putchar('>'); } } - fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), + fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); if (parent) printf(" (from %s)", - diff_unique_abbrev(parent->object.sha1, + find_unique_abbrev(parent->object.sha1, abbrev_commit)); show_decorations(opt, commit); printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET)); diff --git a/merge-recursive.c b/merge-recursive.c index b97026bd5c..ee853b990d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -237,7 +237,7 @@ static int save_files_dirs(const unsigned char *sha1, string_list_insert(newpath, &o->current_file_set); free(newpath); - return READ_TREE_RECURSIVE; + return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } static int get_files_dirs(struct merge_options *o, struct tree *tree) @@ -363,56 +363,97 @@ const char *make_relative_path(const char *abs, const char *base) } /* - * path = absolute path - * buf = buffer of at least max(2, strlen(path)+1) bytes - * It is okay if buf == path, but they should not overlap otherwise. + * It is okay if dst == src, but they should not overlap otherwise. * - * Performs the following normalizations on path, storing the result in buf: - * - Removes trailing slashes. - * - Removes empty components. + * Performs the following normalizations on src, storing the result in dst: + * - Ensures that components are separated by '/' (Windows only) + * - Squashes sequences of '/'. * - Removes "." components. * - Removes ".." components, and the components the precede them. - * "" and paths that contain only slashes are normalized to "/". - * Returns the length of the output. + * Returns failure (non-zero) if a ".." component appears as first path + * component anytime during the normalization. Otherwise, returns success (0). * * Note that this function is purely textual. It does not follow symlinks, * verify the existence of the path, or make any system calls. */ -int normalize_absolute_path(char *buf, const char *path) +int normalize_path_copy(char *dst, const char *src) { - const char *comp_start = path, *comp_end = path; - char *dst = buf; - int comp_len; - assert(buf); - assert(path); - - while (*comp_start) { - assert(*comp_start == '/'); - while (*++comp_end && *comp_end != '/') - ; /* nothing */ - comp_len = comp_end - comp_start; - - if (!strncmp("/", comp_start, comp_len) || - !strncmp("/.", comp_start, comp_len)) - goto next; - - if (!strncmp("/..", comp_start, comp_len)) { - while (dst > buf && *--dst != '/') - ; /* nothing */ - goto next; - } + char *dst0; - memmove(dst, comp_start, comp_len); - dst += comp_len; - next: - comp_start = comp_end; + if (has_dos_drive_prefix(src)) { + *dst++ = *src++; + *dst++ = *src++; } + dst0 = dst; - if (dst == buf) + if (is_dir_sep(*src)) { *dst++ = '/'; + while (is_dir_sep(*src)) + src++; + } + + for (;;) { + char c = *src; + + /* + * A path component that begins with . could be + * special: + * (1) "." and ends -- ignore and terminate. + * (2) "./" -- ignore them, eat slash and continue. + * (3) ".." and ends -- strip one and terminate. + * (4) "../" -- strip one, eat slash and continue. + */ + if (c == '.') { + if (!src[1]) { + /* (1) */ + src++; + } else if (is_dir_sep(src[1])) { + /* (2) */ + src += 2; + while (is_dir_sep(*src)) + src++; + continue; + } else if (src[1] == '.') { + if (!src[2]) { + /* (3) */ + src += 2; + goto up_one; + } else if (is_dir_sep(src[2])) { + /* (4) */ + src += 3; + while (is_dir_sep(*src)) + src++; + goto up_one; + } + } + } + /* copy up to the next '/', and eat all '/' */ + while ((c = *src++) != '\0' && !is_dir_sep(c)) + *dst++ = c; + if (is_dir_sep(c)) { + *dst++ = '/'; + while (is_dir_sep(c)) + c = *src++; + src--; + } else if (!c) + break; + continue; + + up_one: + /* + * dst0..dst is prefix portion, and dst[-1] is '/'; + * go up one level. + */ + dst--; /* go to trailing '/' */ + if (dst <= dst0) + return -1; + /* Windows: dst[-1] cannot be backslash anymore */ + while (dst0 < dst && dst[-1] != '/') + dst--; + } *dst = '\0'; - return dst - buf; + return 0; } /* @@ -438,15 +479,16 @@ int longest_ancestor_length(const char *path, const char *prefix_list) return -1; for (colon = ceil = prefix_list; *colon; ceil = colon+1) { - for (colon = ceil; *colon && *colon != ':'; colon++); + for (colon = ceil; *colon && *colon != PATH_SEP; colon++); len = colon - ceil; if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil)) continue; strlcpy(buf, ceil, len+1); - len = normalize_absolute_path(buf, buf); - /* Strip "trailing slashes" from "/". */ - if (len == 1) - len = 0; + if (normalize_path_copy(buf, buf) < 0) + continue; + len = strlen(buf); + if (len > 0 && buf[len-1] == '/') + buf[--len] = '\0'; if (!strncmp(path, buf, len) && path[len] == '/' && @@ -210,15 +210,13 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb, while (parent) { struct commit *p = parent->item; const char *hex = NULL; - const char *dots; if (abbrev) hex = find_unique_abbrev(p->object.sha1, abbrev); if (!hex) hex = sha1_to_hex(p->object.sha1); - dots = (abbrev && strlen(hex) != 40) ? "..." : ""; parent = parent->next; - strbuf_addf(sb, " %s%s", hex, dots); + strbuf_addf(sb, " %s", hex); } strbuf_addch(sb, '\n'); } @@ -275,10 +275,8 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) list = get_ref_dir(ref, list); continue; } - if (!resolve_ref(ref, sha1, 1, &flag)) { - error("%s points nowhere!", ref); - continue; - } + if (!resolve_ref(ref, sha1, 1, &flag)) + hashclr(sha1); list = add_ref(ref, sha1, flag, list, NULL); } free(ref); @@ -287,6 +285,35 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) return sort_ref_list(list); } +struct warn_if_dangling_data { + const char *refname; + const char *msg_fmt; +}; + +static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct warn_if_dangling_data *d = cb_data; + const char *resolves_to; + unsigned char junk[20]; + + if (!(flags & REF_ISSYMREF)) + return 0; + + resolves_to = resolve_ref(refname, junk, 0, NULL); + if (!resolves_to || strcmp(resolves_to, d->refname)) + return 0; + + printf(d->msg_fmt, refname); + return 0; +} + +void warn_dangling_symref(const char *msg_fmt, const char *refname) +{ + struct warn_if_dangling_data data = { refname, msg_fmt }; + for_each_rawref(warn_if_dangling_symref, &data); +} + static struct ref_list *get_loose_refs(void) { if (!cached_refs.did_loose) { @@ -498,16 +525,19 @@ int read_ref(const char *ref, unsigned char *sha1) return -1; } +#define DO_FOR_EACH_INCLUDE_BROKEN 01 static int do_one_ref(const char *base, each_ref_fn fn, int trim, - void *cb_data, struct ref_list *entry) + int flags, void *cb_data, struct ref_list *entry) { if (strncmp(base, entry->name, trim)) return 0; - if (is_null_sha1(entry->sha1)) - return 0; - if (!has_sha1_file(entry->sha1)) { - error("%s does not point to a valid object!", entry->name); - return 0; + if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { + if (is_null_sha1(entry->sha1)) + return 0; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } } current_ref = entry; return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); @@ -561,7 +591,7 @@ fallback: } static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, - void *cb_data) + int flags, void *cb_data) { int retval = 0; struct ref_list *packed = get_packed_refs(); @@ -570,7 +600,7 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, struct ref_list *extra; for (extra = extra_refs; extra; extra = extra->next) - retval = do_one_ref(base, fn, trim, cb_data, extra); + retval = do_one_ref(base, fn, trim, flags, cb_data, extra); while (packed && loose) { struct ref_list *entry; @@ -586,13 +616,13 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, entry = packed; packed = packed->next; } - retval = do_one_ref(base, fn, trim, cb_data, entry); + retval = do_one_ref(base, fn, trim, flags, cb_data, entry); if (retval) goto end_each; } for (packed = packed ? packed : loose; packed; packed = packed->next) { - retval = do_one_ref(base, fn, trim, cb_data, packed); + retval = do_one_ref(base, fn, trim, flags, cb_data, packed); if (retval) goto end_each; } @@ -614,22 +644,28 @@ int head_ref(each_ref_fn fn, void *cb_data) int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, cb_data); + return do_for_each_ref("refs/", fn, 0, 0, cb_data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags/", fn, 10, cb_data); + return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data); } int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads/", fn, 11, cb_data); + return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data); } int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes/", fn, 13, cb_data); + return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data); +} + +int for_each_rawref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref("refs/", fn, 0, + DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } /* @@ -24,6 +24,11 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +/* can be used to learn about broken ref and symref */ +extern int for_each_rawref(each_ref_fn, void *); + +extern void warn_dangling_symref(const char *msg_fmt, const char *refname); + /* * Extra refs will be listed by for_each_ref() before any actual refs * for the duration of this process or until clear_extra_refs() is @@ -290,7 +290,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) hex = xstrdup(sha1_to_hex(sha1)); string_list_insert(path, rr)->util = hex; if (mkdir(git_path("rr-cache/%s", hex), 0755)) - continue;; + continue; handle_file(path, NULL, rr_path(hex, "preimage")); fprintf(stderr, "Recorded preimage for '%s'\n", path); } diff --git a/revision.c b/revision.c index 8603c14581..286e416b75 100644 --- a/revision.c +++ b/revision.c @@ -1738,14 +1738,16 @@ static struct commit *get_revision_1(struct rev_info *revs) (commit->date < revs->max_age)) continue; if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) - return NULL; + die("Failed to traverse parents of commit %s", + sha1_to_hex(commit->object.sha1)); } switch (simplify_commit(revs, commit)) { case commit_ignore: continue; case commit_error: - return NULL; + die("Failed to simplify parents of commit %s", + sha1_to_hex(commit->object.sha1)); default: return commit; } @@ -4,92 +4,6 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; -static int sanitary_path_copy(char *dst, const char *src) -{ - char *dst0; - - if (has_dos_drive_prefix(src)) { - *dst++ = *src++; - *dst++ = *src++; - } - dst0 = dst; - - if (is_dir_sep(*src)) { - *dst++ = '/'; - while (is_dir_sep(*src)) - src++; - } - - for (;;) { - char c = *src; - - /* - * A path component that begins with . could be - * special: - * (1) "." and ends -- ignore and terminate. - * (2) "./" -- ignore them, eat slash and continue. - * (3) ".." and ends -- strip one and terminate. - * (4) "../" -- strip one, eat slash and continue. - */ - if (c == '.') { - if (!src[1]) { - /* (1) */ - src++; - } else if (is_dir_sep(src[1])) { - /* (2) */ - src += 2; - while (is_dir_sep(*src)) - src++; - continue; - } else if (src[1] == '.') { - if (!src[2]) { - /* (3) */ - src += 2; - goto up_one; - } else if (is_dir_sep(src[2])) { - /* (4) */ - src += 3; - while (is_dir_sep(*src)) - src++; - goto up_one; - } - } - } - - /* copy up to the next '/', and eat all '/' */ - while ((c = *src++) != '\0' && !is_dir_sep(c)) - *dst++ = c; - if (is_dir_sep(c)) { - *dst++ = '/'; - while (is_dir_sep(c)) - c = *src++; - src--; - } else if (!c) - break; - continue; - - up_one: - /* - * dst0..dst is prefix portion, and dst[-1] is '/'; - * go up one level. - */ - dst -= 2; /* go past trailing '/' if any */ - if (dst < dst0) - return -1; - while (1) { - if (dst <= dst0) - break; - c = *dst--; - if (c == '/') { /* MinGW: cannot be '\\' anymore */ - dst += 2; - break; - } - } - } - *dst = '\0'; - return 0; -} - const char *prefix_path(const char *prefix, int len, const char *path) { const char *orig = path; @@ -101,7 +15,7 @@ const char *prefix_path(const char *prefix, int len, const char *path) memcpy(sanitized, prefix, len); strcpy(sanitized + len, path); } - if (sanitary_path_copy(sanitized, sanitized)) + if (normalize_path_copy(sanitized, sanitized)) goto error_out; if (is_absolute_path(orig)) { const char *work_tree = get_git_work_tree(); diff --git a/sha1_file.c b/sha1_file.c index 8868b800cb..5b6e0f61fa 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -689,6 +689,7 @@ void free_pack_by_name(const char *pack_name) while (*pp) { p = *pp; if (strcmp(pack_name, p->pack_name) == 0) { + clear_delta_base_cache(); close_pack_windows(p); if (p->pack_fd != -1) close(p->pack_fd); @@ -1663,6 +1664,13 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent) } } +void clear_delta_base_cache(void) +{ + unsigned long p; + for (p = 0; p < MAX_DELTA_CACHE; p++) + release_delta_base_cache(&delta_base_cache[p]); +} + static void add_delta_base_cache(struct packed_git *p, off_t base_offset, void *base, unsigned long base_size, enum object_type type) { diff --git a/sha1_name.c b/sha1_name.c index 5d0ac0263d..2f75179f4c 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -268,16 +268,19 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) char fullref[PATH_MAX]; unsigned char sha1_from_ref[20]; unsigned char *this_result; + int flag; this_result = refs_found ? sha1_from_ref : sha1; mksnpath(fullref, sizeof(fullref), *p, len, str); - r = resolve_ref(fullref, this_result, 1, NULL); + r = resolve_ref(fullref, this_result, 1, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); if (!warn_ambiguous_refs) break; - } + } else if ((flag & REF_ISSYMREF) && + (len != 4 || strcmp(str, "HEAD"))) + warning("ignoring dangling symref %s.", fullref); } free(last_branch); return refs_found; diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 6e7501f352..4ed1f0b4dd 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -8,36 +8,37 @@ test_description='Test various path utilities' . ./test-lib.sh norm_abs() { - test_expect_success "normalize absolute" \ - "test \$(test-path-utils normalize_absolute_path '$1') = '$2'" + test_expect_success "normalize absolute: $1 => $2" \ + "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } ancestor() { - test_expect_success "longest ancestor" \ - "test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'" + test_expect_success "longest ancestor: $1 $2 => $3" \ + "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'" } -norm_abs "" / +norm_abs "" "" norm_abs / / norm_abs // / norm_abs /// / norm_abs /. / norm_abs /./ / -norm_abs /./.. / -norm_abs /../. / -norm_abs /./../.// / +norm_abs /./.. ++failed++ +norm_abs /../. ++failed++ +norm_abs /./../.// ++failed++ norm_abs /dir/.. / norm_abs /dir/sub/../.. / +norm_abs /dir/sub/../../.. ++failed++ norm_abs /dir /dir -norm_abs /dir// /dir +norm_abs /dir// /dir/ norm_abs /./dir /dir -norm_abs /dir/. /dir -norm_abs /dir///./ /dir -norm_abs /dir//sub/.. /dir -norm_abs /dir/sub/../ /dir -norm_abs //dir/sub/../. /dir -norm_abs /dir/s1/../s2/ /dir/s2 -norm_abs /d1/s1///s2/..//../s3/ /d1/s3 +norm_abs /dir/. /dir/ +norm_abs /dir///./ /dir/ +norm_abs /dir//sub/.. /dir/ +norm_abs /dir/sub/../ /dir/ +norm_abs //dir/sub/../. /dir/ +norm_abs /dir/s1/../s2/ /dir/s2/ +norm_abs /d1/s1///s2/..//../s3/ /d1/s3/ norm_abs /d1/s1//../s2/../../d2 /d2 norm_abs /d1/.../d2 /d1/.../d2 norm_abs /d1/..././../d2 /d1/d2 diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh new file mode 100755 index 0000000000..315b9b3f10 --- /dev/null +++ b/t/t0100-previous.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='previous branch syntax @{-n}' + +. ./test-lib.sh + +test_expect_success 'branch -d @{-1}' ' + test_commit A && + git checkout -b junk && + git checkout - && + test "$(git symbolic-ref HEAD)" = refs/heads/master && + git branch -d @{-1} && + test_must_fail git rev-parse --verify refs/heads/junk +' + +test_expect_success 'branch -d @{-12} when there is not enough switches yet' ' + git reflog expire --expire=now && + git checkout -b junk2 && + git checkout - && + test "$(git symbolic-ref HEAD)" = refs/heads/master && + test_must_fail git branch -d @{-12} && + git rev-parse --verify refs/heads/master +' + +test_expect_success 'merge @{-1}' ' + git checkout A && + test_commit B && + git checkout A && + test_commit C && + git branch -f master B && + git branch -f other && + git checkout other && + git checkout master && + git merge @{-1} && + git cat-file commit HEAD | grep "Merge branch '\''other'\''" +' + +test_expect_success 'merge @{-1} when there is not enough switches yet' ' + git reflog expire --expire=now && + git checkout -f master && + git reset --hard B && + git branch -f other C && + git checkout other && + git checkout master && + test_must_fail git merge @{-12} +' + +test_done + diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh new file mode 100755 index 0000000000..7fa5f5b22a --- /dev/null +++ b/t/t1401-symbolic-ref.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='basic symbolic-ref tests' +. ./test-lib.sh + +# If the tests munging HEAD fail, they can break detection of +# the git repo, meaning that further tests will operate on +# the surrounding git repo instead of the trash directory. +reset_to_sane() { + echo ref: refs/heads/foo >.git/HEAD +} + +test_expect_success 'symbolic-ref writes HEAD' ' + git symbolic-ref HEAD refs/heads/foo && + echo ref: refs/heads/foo >expect && + test_cmp expect .git/HEAD +' + +test_expect_success 'symbolic-ref reads HEAD' ' + echo refs/heads/foo >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic-ref refuses non-ref for HEAD' ' + test_must_fail git symbolic-ref HEAD foo +' +reset_to_sane + +test_expect_success 'symbolic-ref refuses bare sha1' ' + echo content >file && git add file && git commit -m one + test_must_fail git symbolic-ref HEAD `git rev-parse HEAD` +' +reset_to_sane + +test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 85da4caa7e..48ee07779d 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -26,21 +26,28 @@ test_rev_parse() { "test '$1' = \"\$(git rev-parse --show-prefix)\"" shift [ $# -eq 0 ] && return + + test_expect_success "$name: git-dir" \ + "test '$1' = \"\$(git rev-parse --git-dir)\"" + shift + [ $# -eq 0 ] && return } -# label is-bare is-inside-git is-inside-work prefix +# label is-bare is-inside-git is-inside-work prefix git-dir + +ROOT=$(pwd) -test_rev_parse toplevel false false true '' +test_rev_parse toplevel false false true '' .git cd .git || exit 1 -test_rev_parse .git/ false true false '' +test_rev_parse .git/ false true false '' . cd objects || exit 1 -test_rev_parse .git/objects/ false true false '' +test_rev_parse .git/objects/ false true false '' "$ROOT/.git" cd ../.. || exit 1 mkdir -p sub/dir || exit 1 cd sub/dir || exit 1 -test_rev_parse subdirectory false false true sub/dir/ +test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git" cd ../.. || exit 1 git config core.bare true diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 27dc6c55d5..f6a6f839a1 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -92,13 +92,6 @@ cd sub/dir || exit 1 test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/ cd ../../../.. || exit 1 -test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' ' - (expected=$(pwd)/repo.git && - cd repo.git/refs && - unset GIT_DIR && - test "$expected" = "$(git rev-parse --git-dir)") -' - test_expect_success 'repo finds its work tree' ' (cd repo.git && : > work/sub/dir/untracked && diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh index 91b704a3a4..e377d48902 100755 --- a/t/t1504-ceiling-dirs.sh +++ b/t/t1504-ceiling-dirs.sh @@ -93,13 +93,13 @@ GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi" test_prefix subdir_ceil_at_subdi_slash "sub/dir/" -GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub" +GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub" test_fail second_of_two -GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar" +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar" test_fail first_of_two -GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar" +GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar" test_fail second_of_three diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index b7a670ef40..8c0c5f5982 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -14,7 +14,8 @@ export GIT_AUTHOR_EMAIL test_expect_success \ 'prepare repository with topic branches' \ - 'echo First > A && + 'git config core.logAllRefUpdates true && + echo First > A && git update-index --add A && git commit -m "Add A." && git checkout -b my-topic-branch && @@ -84,4 +85,14 @@ test_expect_success 'rebase a single mode change' ' GIT_TRACE=1 git rebase master ' +test_expect_success 'HEAD was detached during rebase' ' + test $(git rev-parse HEAD@{1}) != $(git rev-parse modechange@{1}) +' + +test_expect_success 'Show verbose error when HEAD could not be detached' ' + : > B && + test_must_fail git rebase topic 2> output.err > output.out && + grep "Untracked working tree file .B. would be overwritten" output.err +' + test_done diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ index 3ceb8e73c5..bd7f5c0f70 100644 --- a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ +++ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ @@ -1,6 +1,6 @@ $ git log --patch-with-stat --summary master -- dir/ commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_master index 43d77761f9..14595a614c 100644 --- a/t/t4013/diff.log_--patch-with-stat_master +++ b/t/t4013/diff.log_--patch-with-stat_master @@ -1,6 +1,6 @@ $ git log --patch-with-stat master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_ index 5187a26816..5a4e72765d 100644 --- a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ +++ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_ @@ -1,6 +1,6 @@ $ git log --patch-with-stat master -- dir/ commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master index c9640976a8..df0aaa9f2c 100644 --- a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root --cc --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master index ad050af55f..c11b5f2c7f 100644 --- a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master index 628c6c03bc..5f0c98f9ce 100644 --- a/t/t4013/diff.log_--root_--patch-with-stat_master +++ b/t/t4013/diff.log_--root_--patch-with-stat_master @@ -1,6 +1,6 @@ $ git log --root --patch-with-stat master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master index 5d4e0f13b5..e62c368dc6 100644 --- a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root -c --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_-p_master b/t/t4013/diff.log_--root_-p_master index 217a2eb203..b42c334439 100644 --- a/t/t4013/diff.log_--root_-p_master +++ b/t/t4013/diff.log_--root_-p_master @@ -1,6 +1,6 @@ $ git log --root -p master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_master b/t/t4013/diff.log_--root_master index e17ccfc234..e8f46159da 100644 --- a/t/t4013/diff.log_--root_master +++ b/t/t4013/diff.log_--root_master @@ -1,6 +1,6 @@ $ git log --root master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_-p_master b/t/t4013/diff.log_-p_master index f8fefef2c3..bf1326dc36 100644 --- a/t/t4013/diff.log_-p_master +++ b/t/t4013/diff.log_-p_master @@ -1,6 +1,6 @@ $ git log -p master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_master index e9d9e7b40a..a8f6ce5abd 100644 --- a/t/t4013/diff.log_master +++ b/t/t4013/diff.log_master @@ -1,6 +1,6 @@ $ git log master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.show_master b/t/t4013/diff.show_master index 9e6e1f2710..fb08ce0e46 100644 --- a/t/t4013/diff.show_master +++ b/t/t4013/diff.show_master @@ -1,6 +1,6 @@ $ git show master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master index 5facf2543d..e96ff1fb8c 100644 --- a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master +++ b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git whatchanged --root --cc --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master index 10f6767e49..c0aff68ef6 100644 --- a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master +++ b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git whatchanged --root -c --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index caea292f15..281680d95a 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -128,4 +128,12 @@ test_expect_success 'force diff with "diff"' ' test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual ' +test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' ' + echo anotherfile > file2 && + git add file2 && + git commit -m "added 2nd file" && + echo modified >file2 && + GIT_EXTERNAL_DIFF=echo git diff +' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 771c0a06a4..55ed7c7935 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -112,4 +112,42 @@ test_expect_success 'prune: do not prune heads listed as an argument' ' ' +test_expect_success 'gc --no-prune' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime =-$((86400*5001)) $BLOB_FILE && + git config gc.pruneExpire 2.days.ago && + git gc --no-prune && + test 1 = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE + +' + +test_expect_success 'gc respects gc.pruneExpire' ' + + git config gc.pruneExpire 5002.days.ago && + git gc && + test -f $BLOB_FILE && + git config gc.pruneExpire 5000.days.ago && + git gc && + test ! -f $BLOB_FILE + +' + +test_expect_success 'gc --prune=<date>' ' + + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test-chmtime =-$((86400*5001)) $BLOB_FILE && + git gc --prune=5002.days.ago && + test -f $BLOB_FILE && + git gc --prune=5000.days.ago && + test ! -f $BLOB_FILE + +' + test_done diff --git a/t/t5307-pack-missing-commit.sh b/t/t5307-pack-missing-commit.sh new file mode 100755 index 0000000000..ae52a1882d --- /dev/null +++ b/t/t5307-pack-missing-commit.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='pack should notice missing commit objects' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 + do + echo "$i" >"file$i" && + git add "file$i" && + test_tick && + git commit -m "$i" && + git tag "tag$i" + done && + obj=$(git rev-parse --verify tag3) && + fanout=$(expr "$obj" : "\(..\)") && + remainder=$(expr "$obj" : "..\(.*\)") && + rm -f ".git/objects/$fanout/$remainder" +' + +test_expect_success 'check corruption' ' + test_must_fail git fsck +' + +test_expect_success 'rev-list notices corruption (1)' ' + test_must_fail git rev-list HEAD +' + +test_expect_success 'rev-list notices corruption (2)' ' + test_must_fail git rev-list --objects HEAD +' + +test_expect_success 'pack-objects notices corruption' ' + echo HEAD | + test_must_fail git pack-objects --revs pack +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index b21317d685..f2d5581b12 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -32,9 +32,7 @@ test_expect_success setup ' done && git update-ref HEAD "$commit" && git clone ./. victim && - cd victim && - git log && - cd .. && + ( cd victim && git log ) && git update-ref HEAD "$zero" && parent=$zero && i=0 && @@ -59,88 +57,84 @@ test_expect_success 'pack the source repository' ' ' test_expect_success 'pack the destination repository' ' + ( cd victim && git repack -a -d && - git prune && - cd .. + git prune + ) ' -test_expect_success \ - 'pushing rewound head should not barf but require --force' ' - # should not fail but refuse to update. - if git send-pack ./victim/.git/ master - then - # now it should fail with Pasky patch - echo >&2 Gaah, it should have failed. - false - else - echo >&2 Thanks, it correctly failed. - true - fi && - if cmp victim/.git/refs/heads/master .git/refs/heads/master - then - # should have been left as it was! - false - else - true - fi && +test_expect_success 'refuse pushing rewound head without --force' ' + pushed_head=$(git rev-parse --verify master) && + victim_orig=$(cd victim && git rev-parse --verify master) && + test_must_fail git send-pack ./victim master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_head" = "$victim_orig" && # this should update - git send-pack --force ./victim/.git/ master && - cmp victim/.git/refs/heads/master .git/refs/heads/master + git send-pack --force ./victim master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_head" = "$pushed_head" ' test_expect_success \ 'push can be used to delete a ref' ' - cd victim && - git branch extra master && - cd .. && - test -f victim/.git/refs/heads/extra && - git send-pack ./victim/.git/ :extra master && - ! test -f victim/.git/refs/heads/extra + ( cd victim && git branch extra master ) && + git send-pack ./victim :extra master && + ( cd victim && + test_must_fail git rev-parse --verify extra ) ' -unset GIT_CONFIG -HOME=`pwd`/no-such-directory -export HOME ;# this way we force the victim/.git/config to be used. - -test_expect_success \ - 'pushing a delete should be denied with denyDeletes' ' - cd victim && - git config receive.denyDeletes true && - git branch extra master && - cd .. && - test -f victim/.git/refs/heads/extra && - test_must_fail git send-pack ./victim/.git/ :extra master +test_expect_success 'refuse deleting push with denyDeletes' ' + ( + cd victim && + ( git branch -D extra || : ) && + git config receive.denyDeletes true && + git branch extra master + ) && + test_must_fail git send-pack ./victim :extra master ' -rm -f victim/.git/refs/heads/extra -test_expect_success \ - 'pushing with --force should be denied with denyNonFastforwards' ' - cd victim && - git config receive.denyNonFastforwards true && - cd .. && - git update-ref refs/heads/master master^ || return 1 - git send-pack --force ./victim/.git/ master && return 1 - ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master +test_expect_success 'denyNonFastforwards trumps --force' ' + ( + cd victim && + ( git branch -D extra || : ) && + git config receive.denyNonFastforwards true + ) && + victim_orig=$(cd victim && git rev-parse --verify master) && + test_must_fail git send-pack --force ./victim master^:master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_orig" = "$victim_head" ' -test_expect_success \ - 'pushing does not include non-head refs' ' - mkdir parent && cd parent && - git init && touch file && git add file && git commit -m add && - cd .. && - git clone parent child && cd child && git push --all && - cd ../parent && - git branch -a >branches && ! grep origin/master branches +test_expect_success 'push --all excludes remote tracking hierarchy' ' + mkdir parent && + ( + cd parent && + git init && : >file && git add file && git commit -m add + ) && + git clone parent child && + ( + cd child && git push --all + ) && + ( + cd parent && + test -z "$(git for-each-ref refs/remotes/origin)" + ) ' rewound_push_setup() { rm -rf parent child && - mkdir parent && cd parent && - git init && echo one >file && git add file && git commit -m one && - echo two >file && git commit -a -m two && - cd .. && - git clone parent child && cd child && git reset --hard HEAD^ + mkdir parent && + ( + cd parent && + git init && + echo one >file && git add file && git commit -m one && + echo two >file && git commit -a -m two + ) && + git clone parent child && + ( + cd child && git reset --hard HEAD^ + ) } rewound_push_succeeded() { @@ -156,30 +150,57 @@ rewound_push_failed() { fi } -test_expect_success \ - 'pushing explicit refspecs respects forcing' ' +test_expect_success 'pushing explicit refspecs respects forcing' ' rewound_push_setup && - if git send-pack ../parent/.git refs/heads/master:refs/heads/master - then - false - else - true - fi && rewound_push_failed && - git send-pack ../parent/.git +refs/heads/master:refs/heads/master && - rewound_push_succeeded + parent_orig=$(cd parent && git rev-parse --verify master) && + ( + cd child && + test_must_fail git send-pack ../parent \ + refs/heads/master:refs/heads/master + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + test "$parent_orig" = "$parent_head" && + ( + cd child && + git send-pack ../parent \ + +refs/heads/master:refs/heads/master + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd parent && git rev-parse --verify master) && + test "$parent_head" = "$child_head" ' -test_expect_success \ - 'pushing wildcard refspecs respects forcing' ' +test_expect_success 'pushing wildcard refspecs respects forcing' ' rewound_push_setup && - if git send-pack ../parent/.git refs/heads/*:refs/heads/* - then - false - else - true - fi && rewound_push_failed && - git send-pack ../parent/.git +refs/heads/*:refs/heads/* && - rewound_push_succeeded + parent_orig=$(cd parent && git rev-parse --verify master) && + ( + cd child && + test_must_fail git send-pack ../parent \ + "refs/heads/*:refs/heads/*" + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + test "$parent_orig" = "$parent_head" && + ( + cd child && + git send-pack ../parent \ + "+refs/heads/*:refs/heads/*" + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd parent && git rev-parse --verify master) && + test "$parent_head" = "$child_head" +' + +test_expect_success 'warn pushing to delete current branch' ' + rewound_push_setup && + ( + cd child && + git send-pack ../parent :refs/heads/master 2>errs + ) && + grep "warning: to refuse deleting" child/errs && + ( + cd parent && + test_must_fail git rev-parse --verify master + ) ' test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 1f59960d90..eb637184a0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -107,6 +107,32 @@ test_expect_success 'remove remote' ' ) ' +test_expect_success 'remove remote protects non-remote branches' ' +( + cd test && + (cat >expect1 <<EOF +Note: A non-remote branch was not removed; to delete it, use: + git branch -d master +EOF + cat >expect2 <<EOF +Note: Non-remote branches were not removed; to delete them, use: + git branch -d foobranch + git branch -d master +EOF +) && + git tag footag + git config --add remote.oops.fetch "+refs/*:refs/*" && + git remote rm oops 2>actual1 && + git branch foobranch && + git config --add remote.oops.fetch "+refs/*:refs/*" && + git remote rm oops 2>actual2 && + git branch -d foobranch && + git tag -d footag && + test_cmp expect1 actual1 && + test_cmp expect2 actual2 +) +' + cat > test/expect << EOF * remote origin URL: $(pwd)/one @@ -376,4 +402,31 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") ' +test_expect_success 'remote prune to cause a dangling symref' ' + git clone one seven && + ( + cd one && + git checkout side2 && + git branch -D master + ) && + ( + cd seven && + git remote prune origin + ) 2>err && + grep "has become dangling" err && + + : And the dangling symref will not cause other annoying errors + ( + cd seven && + git branch -a + ) 2>err && + ! grep "points nowhere" err + ( + cd seven && + test_must_fail git branch nomore origin + ) 2>err && + grep "dangling symref" err +' + test_done + diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4426df9226..89649e7a9b 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -492,7 +492,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' ' git checkout master && git config receive.denyCurrentBranch warn) && git push testrepo master 2>stderr && - grep "warning.*this may cause confusion" stderr + grep "warning: updating the current branch" stderr ' test_expect_success 'deny push to HEAD of non-bare repository' ' @@ -510,7 +510,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' ' git config receive.denyCurrentBranch true && git config core.bare true) && git push testrepo master 2>stderr && - ! grep "warning.*this may cause confusion" stderr + ! grep "warning: updating the current branch" stderr ' test_expect_success 'allow push to HEAD of non-bare repository (config)' ' @@ -520,7 +520,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' ' git config receive.denyCurrentBranch false ) && git push testrepo master 2>stderr && - ! grep "warning.*this may cause confusion" stderr + ! grep "warning: updating the current branch" stderr ' test_expect_success 'fetch with branches' ' diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index c236b5e83b..11b343274f 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -94,6 +94,13 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' ' ' +test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' ' + + grep -P "\"(?:PUT|MOVE) .+objects/[\da-z]{2}/[\da-z]{38}_[\da-z\-]{40} HTTP/[0-9.]+\" 20\d" \ + < "$HTTPD_ROOT_PATH"/access.log + +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index fe287d31fb..44793f2eee 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -144,4 +144,19 @@ test_expect_success 'clone to an existing path' ' test_must_fail git clone src target-5 ' +test_expect_success 'clone a void' ' + mkdir src-0 && + ( + cd src-0 && git init + ) && + git clone src-0 target-6 && + ( + cd src-0 && test_commit A + ) && + git clone src-0 target-7 && + # There is no reason to insist they are bit-for-bit + # identical, but this test should suffice for now. + test_cmp target-6/.git/config target-7/.git/config +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 8537bf9160..56b5eccdb4 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -39,13 +39,19 @@ test_expect_success 'result is really identical' ' ' test_expect_success 'rewrite bare repository identically' ' - (git config core.bare true && cd .git && git filter-branch branch) + (git config core.bare true && cd .git && + git filter-branch branch > filter-output 2>&1 && + ! fgrep fatal filter-output) ' git config core.bare false test_expect_success 'result is really identical' ' test $H = $(git rev-parse HEAD) ' +test_expect_success 'Fail if commit filter fails' ' + test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD +' + test_expect_success 'rewrite, renaming a specific file' ' git filter-branch -f --tree-filter "mv d doh || :" HEAD ' diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 2ec7ac6a51..b8cb2df667 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -234,4 +234,17 @@ test_expect_success 'gracefully add submodule with a trailing slash' ' ' +test_expect_success 'ls-files gracefully handles trailing slash' ' + + test "init" = "$(git ls-files init/)" + +' + +test_expect_success 'submodule <invalid-path> warns' ' + + git submodule no-such-submodule 2> output.err && + grep "^error: .*no-such-submodule" output.err + +' + test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 09fa5f115c..e768c3eb2d 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -9,38 +9,81 @@ Testing basic merge tool invocation' . ./test-lib.sh +# All the mergetool test work by checking out a temporary branch based +# off 'branch1' and then merging in master and checking the results of +# running mergetool + test_expect_success 'setup' ' echo master >file1 && - git add file1 && + mkdir subdir && + echo master sub >subdir/file3 && + git add file1 subdir/file3 && git commit -m "added file1" && + git checkout -b branch1 master && echo branch1 change >file1 && echo branch1 newfile >file2 && - git add file1 file2 && + echo branch1 sub >subdir/file3 && + git add file1 file2 subdir/file3 && git commit -m "branch1 changes" && - git checkout -b branch2 master && - echo branch2 change >file1 && - echo branch2 newfile >file2 && - git add file1 file2 && - git commit -m "branch2 changes" && + git checkout master && echo master updated >file1 && echo master new >file2 && - git add file1 file2 && - git commit -m "master updates" -' + echo master new sub >subdir/file3 && + git add file1 file2 subdir/file3 && + git commit -m "master updates" && -test_expect_success 'custom mergetool' ' git config merge.tool mytool && git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && - git config mergetool.mytool.trustExitCode true && - git checkout branch1 && + git config mergetool.mytool.trustExitCode true +' + +test_expect_success 'custom mergetool' ' + git checkout -b test1 branch1 && test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file1>/dev/null 2>&1 ) && - ( yes "" | git mergetool file2>/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && test "$(cat file1)" = "master updated" && test "$(cat file2)" = "master new" && - git commit -m "branch1 resolved with mergetool" + test "$(cat subdir/file3)" = "master new sub" && + git commit -m "branch1 resolved with mergetool" ' +test_expect_success 'mergetool crlf' ' + git config core.autocrlf true && + git checkout -b test2 branch1 + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" && + test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" && + test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && + git commit -m "branch1 resolved with mergetool - autocrlf" && + git config core.autocrlf false && + git reset --hard +' + +test_expect_success 'mergetool in subdir' ' + git checkout -b test3 branch1 + cd subdir && ( + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && + test "$(cat file3)" = "master new sub" ) +' + +# We can't merge files from parent directories when running mergetool +# from a subdir. Is this a bug? +# +#test_expect_failure 'mergetool in subdir' ' +# cd subdir && ( +# ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && +# ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && +# test "$(cat ../file1)" = "master updated" && +# test "$(cat ../file2)" = "master new" && +# git commit -m "branch1 resolved with mergetool - subdir" ) +#' + test_done diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh index 704a4f8574..20529a878c 100755 --- a/t/t9131-git-svn-empty-symlink.sh +++ b/t/t9131-git-svn-empty-symlink.sh @@ -87,4 +87,14 @@ test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar' test_expect_success 'get "bar" => symlink fix from svn' \ '(cd x && git svn rebase)' test_expect_success '"bar" becomes a symlink' 'test -L x/bar' + + +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y' +test_expect_success 'disable broken symlink workaround' \ + '(cd y && git config svn.brokenSymlinkWorkaround false)' +test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar' +test_expect_success 'get "bar" => symlink fix from svn' \ + '(cd y && git svn rebase)' +test_expect_success '"bar" does not become a symlink' '! test -L y/bar' + test_done diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh new file mode 100755 index 0000000000..03705fa4ce --- /dev/null +++ b/t/t9135-git-svn-moved-branch-empty-file.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test_description='test moved svn branch with missing empty files' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9135/svn.dump" + ' + +test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x' + +test_expect_success 'test that b1 exists and is empty' ' + (cd x && test -f b1 && ! test -s b1) + ' + +test_done diff --git a/t/t9135/svn.dump b/t/t9135/svn.dump new file mode 100644 index 0000000000..b51c0ccceb --- /dev/null +++ b/t/t9135/svn.dump @@ -0,0 +1,192 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1f80e919-e9e3-4d80-a3ae-d9f21095e27b + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-02-10T19:23:16.424027Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 20 +init standard layout +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:17.195072Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 18 +branch-b off trunk +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:19.160095Z +PROPS-END + +Node-path: branches/branch-b +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + +Revision-number: 3 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 17 +add empty file b1 +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:20.194568Z +PROPS-END + +Node-path: branches/branch-b/b1 +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 8 +branch-c +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.169100Z +PROPS-END + +Node-path: branches/branch-c +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk + + +Revision-number: 5 +Prop-content-length: 126 +Content-length: 126 + +K 7 +svn:log +V 23 +oops, wrong branchpoint +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.253557Z +PROPS-END + +Node-path: branches/branch-c +Node-action: delete + + +Revision-number: 6 +Prop-content-length: 127 +Content-length: 127 + +K 7 +svn:log +V 24 +branch-c off of branch-b +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.314659Z +PROPS-END + +Node-path: branches/branch-c +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/branch-b +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + diff --git a/test-path-utils.c b/test-path-utils.c index 2c0f5a37e8..5168a8e3df 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -2,11 +2,13 @@ int main(int argc, char **argv) { - if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) { + if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { char *buf = xmalloc(PATH_MAX + 1); - int rv = normalize_absolute_path(buf, argv[2]); - assert(strlen(buf) == rv); + int rv = normalize_path_copy(buf, argv[2]); + if (rv) + buf = "++failed++"; puts(buf); + return 0; } if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) { @@ -15,12 +17,16 @@ int main(int argc, char **argv) argc--; argv++; } + return 0; } if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) { int len = longest_ancestor_length(argv[2], argv[3]); printf("%d\n", len); + return 0; } - return 0; + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], + argv[1] ? argv[1] : "(there was none)"); + return 1; } @@ -110,7 +110,7 @@ int read_tree_recursive(struct tree *tree, case 0: continue; case READ_TREE_RECURSIVE: - break;; + break; default: return -1; } @@ -131,6 +131,34 @@ int read_tree_recursive(struct tree *tree, if (retval) return -1; continue; + } else if (S_ISGITLINK(entry.mode)) { + int retval; + struct strbuf path; + unsigned int entrylen; + struct commit *commit; + + entrylen = tree_entry_len(entry.path, entry.sha1); + strbuf_init(&path, baselen + entrylen + 1); + strbuf_add(&path, base, baselen); + strbuf_add(&path, entry.path, entrylen); + strbuf_addch(&path, '/'); + + commit = lookup_commit(entry.sha1); + if (!commit) + die("Commit %s in submodule path %s not found", + sha1_to_hex(entry.sha1), path.buf); + + if (parse_commit(commit)) + die("Invalid commit %s in submodule path %s", + sha1_to_hex(entry.sha1), path.buf); + + retval = read_tree_recursive(commit->tree, + path.buf, path.len, + stage, match, fn, context); + strbuf_release(&path); + if (retval) + return -1; + continue; } } return 0; @@ -246,6 +246,25 @@ int utf8_width(const char **start, size_t *remainder_p) return git_wcwidth(ch); } +/* + * Returns the total number of columns required by a null-terminated + * string, assuming that the string is utf8. Returns strlen() instead + * if the string does not look like a valid utf8 string. + */ +int utf8_strwidth(const char *string) +{ + int width = 0; + const char *orig = string; + + while (1) { + if (!string) + return strlen(orig); + if (!*string) + return width; + width += utf8_width(&string, NULL); + } +} + int is_utf8(const char *text) { while (*text) { @@ -5,6 +5,7 @@ typedef unsigned int ucs_char_t; /* assuming 32bit int */ ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p); int utf8_width(const char **start, size_t *remainder_p); +int utf8_strwidth(const char *string); int is_utf8(const char *text); int is_encoding_utf8(const char *name); @@ -18,7 +18,7 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex) static void report_missing(const struct object *obj) { char missing_hex[41]; - strcpy(missing_hex, sha1_to_hex(obj->sha1));; + strcpy(missing_hex, sha1_to_hex(obj->sha1)); fprintf(stderr, "Cannot obtain needed %s %s\n", obj->type ? typename(obj->type): "object", missing_hex); if (!is_null_sha1(current_commit_sha1)) |