Merge branch 'ar/mksnpath'
authorJunio C Hamano <gitster@pobox.com>
Wed, 5 Nov 2008 19:35:53 +0000 (11:35 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Nov 2008 19:35:53 +0000 (11:35 -0800)
* ar/mksnpath:
Use git_pathdup instead of xstrdup(git_path(...))
git_pathdup: returns xstrdup-ed copy of the formatted path
Fix potentially dangerous use of git_path in ref.c
Add git_snpath: a .git path formatting routine with output buffer
Fix potentially dangerous uses of mkpath and git_path
Fix potentially dangerous uses of mkpath and git_path
Fix mkpath abuse in dwim_ref and dwim_log of sha1_name.c
Add mksnpath which allows you to specify the output buffer

Conflicts:
builtin-revert.c

71 files changed:
Documentation/Makefile
Documentation/RelNotes-1.6.0.4.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.1.txt
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/blame-options.txt
Documentation/config.txt
Documentation/diff-format.txt
Documentation/git-format-patch.txt
Documentation/gitattributes.txt
Documentation/i18n.txt
Documentation/rev-list-options.txt
INSTALL
Makefile
builtin-blame.c
builtin-branch.c
builtin-checkout-index.c
builtin-pack-objects.c
builtin-receive-pack.c
builtin-remote.c
builtin-reset.c
builtin-revert.c
builtin-send-pack.c
builtin-tag.c
builtin-update-ref.c
cache.h
commit.h
compat/cygwin.c
connect.c
contrib/completion/git-completion.bash
contrib/hooks/post-receive-email
diffcore-rename.c
diffcore.h
git-gui/Makefile
git-gui/git-gui--askpass [new file with mode: 0755]
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/choose_repository.tcl
git-gui/lib/diff.tcl
git-gui/lib/index.tcl
git-gui/lib/merge.tcl
git-gui/lib/mergetool.tcl
git-gui/lib/remote.tcl
git-gui/lib/remote_add.tcl [new file with mode: 0644]
git-gui/lib/remote_branch_delete.tcl
git-gui/lib/search.tcl [new file with mode: 0644]
git-gui/lib/spellcheck.tcl
git-gui/lib/sshkey.tcl [new file with mode: 0644]
git-gui/lib/transport.tcl
git-gui/po/de.po
git-rebase--interactive.sh
git-send-email.perl
git-svn.perl
gitweb/gitweb.perl
index-pack.c
path.c
perl/Git.pm
pretty.c
refs.c
t/t1400-update-ref.sh
t/t3200-branch.sh
t/t3411-rebase-preserve-around-merges.sh [new file with mode: 0644]
t/t5000-tar-tree.sh
t/t5302-pack-index.sh
t/t5400-send-pack.sh
t/t8005-blame-i18n.sh [new file with mode: 0755]
t/t8005/cp1251.txt [new file with mode: 0644]
t/t8005/sjis.txt [new file with mode: 0644]
t/t8005/utf8.txt [new file with mode: 0644]
test-chmtime.c
unpack-trees.c
index e33ddcb250bf14e5374df7b86f54a49ed5f7cb07..c34c1cae2072fa27de32f553866426bbc01436d5 100644 (file)
@@ -87,7 +87,9 @@ man7: $(DOC_MAN7)
 
 info: git.info gitman.info
 
-install: man
+install: install-man
+
+install-man: man
        $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
        $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir)
        $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir)
@@ -220,7 +222,9 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
 install-webdoc : html
        sh ./install-webdoc.sh $(WEBDOC_DEST)
 
-quick-install:
+quick-install: quick-install-man
+
+quick-install-man:
        sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
 
 quick-install-html:
diff --git a/Documentation/RelNotes-1.6.0.4.txt b/Documentation/RelNotes-1.6.0.4.txt
new file mode 100644 (file)
index 0000000..db1002e
--- /dev/null
@@ -0,0 +1,40 @@
+GIT v1.6.0.4 Release Notes
+==========================
+
+Fixes since v1.6.0.3
+--------------------
+
+* 'git add -p' said "No changes" when only binary files were changed.
+
+* 'git archive' did not work correctly in bare repositories.
+
+* 'git checkout -t -b newbranch' when you are on detached HEAD was broken.
+
+* when we refuse to detect renames because there are too many new or
+  deleted files, 'git diff' did not say how many there are.
+
+* 'git push --mirror' tried and failed to push the stash; there is no
+  point in sending it to begin with.
+
+* 'git pull $there $branch:$current_branch' did not work when you were on
+  a branch yet to be born.
+
+* when giving up resolving a conflicted merge, 'git reset --hard' failed
+  to remove new paths from the working tree.
+
+* 'git send-email' had a small fd leak while scanning directory.
+
+* 'git status' incorrectly reported a submodule directory as an untracked
+  directory.
+
+* 'git svn' used deprecated 'git-foo' form of subcommand invocaition.
+
+* Plugged small memleaks here and there.
+
+* Also contains many documentation updates.
+
+--
+exec >/var/tmp/1
+O=v1.6.0.3-34-gf6276b7
+echo O=$(git describe maint)
+git shortlog --no-merges $O..maint
index afd1150eaa4fc17600dd948f8d7dc4ec266aa162..6f84c0c2f8ec38763d819efae2cbfa045dc78af2 100644 (file)
@@ -62,6 +62,11 @@ on.
 * "git bisect" is careful about a user mistake and suggests testing of
   merge base first when good is not a strict ancestor of bad.
 
+* "git blame" re-encodes the commit metainfo to UTF-8 from i18n.commitEncoding
+  by default.
+
+* "git check-attr --stdin" can check attributes for multiple paths.
+
 * "git checkout --track origin/hack" used to be a syntax error.  It now
   DWIMs to create a corresponding local branch "hack", i.e. acts as if you
   said "git checkout --track -b hack origin/hack".
@@ -74,6 +79,8 @@ on.
 
 * "git cherry-pick" can also utilize rerere for conflict resolution.
 
+* "git clone" learned to be verbose with -v
+
 * "git commit --author=$name" can look up author name from existing
   commits.
 
@@ -85,6 +92,12 @@ on.
 
 * "git daemon" learned --max-connections=<count> option.
 
+* "git daemon" exports REMOTE_ADDR to record client address, so that
+  spawned programs can act differently on it.
+
+* "git describe --tags" favours closer lightweight tags than farther
+  annotated tags now.
+
 * "git diff" learned to mimic --suppress-blank-empty from GNU diff via a
   configuration option.
 
@@ -99,9 +112,16 @@ on.
 
 * "git diff" hunk header pattern for ObjC has been added.
 
+* a "textconv" filter that makes binary files textual form for human
+   consumption can be specified as an attribute for paths; "git diff"
+   learnt to make use of it.
+
 * "git for-each-ref" learned "refname:short" token that gives an
   unambiguously abbreviated refname.
 
+* Auto-numbering of the subject lines is the default for "git
+  format-patch" now.
+
 * "git grep" learned to accept -z similar to GNU grep.
 
 * "git help" learned to use GIT_MAN_VIEWER environment variable before
@@ -127,9 +147,21 @@ on.
 * "git merge -s $strategy" can use a custom built strategy if you have a
   command "git-merge-$strategy" on your $PATH.
 
+* "git push" can be told to reject deletion of refs with receive.denyDeletes
+  configuration.
+
+* "git rebase" honours pre-rebase hook; use --no-verify to bypass it.
+
+* "git rebase -p" uses interactive rebase machinery now to preserve the merges.
+
 * "git reflog expire branch" can be used in place of "git reflog expire
   refs/heads/branch".
 
+* "git remote show $remote" lists remote branches one-per-line now.
+
+* when giving up resolving a conflicted merge, "git reset --hard" failed
+  to remove new paths from the working tree. [cherry-pick to 'maint'?]
+
 * "git submodule foreach" subcommand allows you to iterate over checked
   out submodules.
 
@@ -138,6 +170,8 @@ on.
 
 * "git svn branch" can create new branches on the other end.
 
+* "gitweb" can use more saner PATH_INFO based URL.
+
 (internal)
 
 * "git hash-object" learned to lie about the path being hashed, so that
@@ -147,6 +181,10 @@ on.
 * various callers of git-merge-recursive avoid forking it as an external
   process.
 
+* Git class defined in "Git.pm" can be subclasses a bit more easily.
+
+* We used to link GNU regex library as a compatibility layer for some platforms,
+  but it turns out it is not necessary on most of them.
 
 Fixes since v1.6.0
 ------------------
@@ -179,6 +217,6 @@ release, unless otherwise noted.
 
 --
 exec >/var/tmp/1
-O=v1.6.0.2-553-g58e0fa5
+O=v1.6.0.3-610-g94dc4b8
 echo O=$(git describe master)
 git shortlog --no-merges $O..master ^maint
index a1e9100f9e3ccb8466ec603e154cb230dc2cb33b..f0295c60f5aceb975575903327228776d6b2bb9e 100644 (file)
@@ -456,3 +456,30 @@ This should help you to submit patches inline using KMail.
 
 5) Back in the compose window: add whatever other text you wish to the
 message, complete the addressing and subject fields, and press send.
+
+
+Gmail
+-----
+
+Submitting properly formatted patches via Gmail is simple now that
+IMAP support is available. First, edit your ~/.gitconfig to specify your
+account settings:
+
+[imap]
+       folder = "[Gmail]/Drafts"
+       host = imaps://imap.gmail.com
+       user = user@gmail.com
+       pass = p4ssw0rd
+       port = 993
+       sslverify = false
+
+Next, ensure that your Gmail settings are correct. In "Settings" the
+"Use Unicode (UTF-8) encoding for outgoing messages" should be checked.
+
+Once your commits are ready to send to the mailing list, run the following
+command to send the patch emails to your Gmail Drafts folder.
+
+       $ git format-patch -M --stdout origin/master | git imap-send
+
+Go to your Gmail account, open the Drafts folder, find the patch email, fill
+in the To: and CC: fields and send away!
index 40d43b78ee9d6c3827bcf631c1f41f54d0e3dfbc..2da867d2f8dd1e5272d33571062bda5f169cd278 100644 (file)
@@ -40,6 +40,26 @@ endif::doctype-manpage[]
 </literallayout>
 {title#}</example>
 endif::docbook-xsl-172[]
+
+ifdef::docbook-xsl-172[]
+ifdef::doctype-manpage[]
+# The following two small workarounds insert a simple paragraph after screen
+[listingblock]
+<example><title>{title}</title>
+<screen>
+|
+</screen><simpara></simpara>
+{title#}</example>
+
+[verseblock]
+<formalpara{id? id="{id}"}><title>{title}</title><para>
+{title%}<literallayout{id? id="{id}"}>
+{title#}<literallayout>
+|
+</literallayout><simpara></simpara>
+{title#}</para></formalpara>
+endif::doctype-manpage[]
+endif::docbook-xsl-172[]
 endif::backend-docbook[]
 
 ifdef::doctype-manpage[]
index 5428111d732cb38dbb257ddfa860ebd04088b4e9..1ab1b96cf9e4c72a7d4e2a8a1d5dd8f016ac9d79 100644 (file)
@@ -49,6 +49,13 @@ of lines before or after the line given by <start>.
        Show the result incrementally in a format designed for
        machine consumption.
 
+--encoding=<encoding>::
+       Specifies the encoding used to output author names
+       and commit summaries. Setting it to `none` makes blame
+       output unconverted data. For more information see the
+       discussion about encoding in the linkgit:git-log[1]
+       manual page.
+
 --contents <file>::
        When <rev> is not specified, the command annotates the
        changes starting backwards from the working tree copy.
index 29369d051b1d8248b85ae177151a4ebaa79bce3b..965ed746da0901d84e0fed3f08de2a0d470ca4e4 100644 (file)
@@ -1188,6 +1188,10 @@ receive.unpackLimit::
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
 
+receive.denyDeletes::
+       If set to true, git-receive-pack will deny a ref update that deletes
+       the ref. Use this to prevent such a ref deletion via a push.
+
 receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
        not a fast forward. Use this to prevent such an update via a push,
index 400cbb3b1c120b93278472678ee7bdb87a74f95b..aafd3a394126e4718b593eb5727412e16d2334e4 100644 (file)
@@ -46,6 +46,22 @@ That is, from the left to the right:
 . path for "dst"; only exists for C or R.
 . an LF or a NUL when '-z' option is used, to terminate the record.
 
+Possible status letters are:
+
+- A: addition of a file
+- C: copy of a file into a new one
+- D: deletion of a file
+- M: modification of the contents or mode of a file
+- R: renaming of a file
+- T: change in the type of the file
+- U: file is unmerged (you must complete the merge before it can
+be committed)
+- X: "unknown" change type (most probably a bug, please report it)
+
+Status letters C and M are always followed by a score (denoting the
+percentage of similarity between the source and target of the move or
+copy), and are the only ones to be so.
+
 <sha1> is shown as all 0's if a file is new on the filesystem
 and it is out of sync with the index.
 
index ac36ce8717c7519f86907d2dc0165651bab1da3b..11a7d772618f48b961d41fb5fe1cb269d7ae7cd1 100644 (file)
@@ -46,7 +46,8 @@ applies to that command line and you do not get "everything
 since the beginning of the time".  If you want to format
 everything since project inception to one commit, say "git
 format-patch \--root <commit>" to make it clear that it is the
-latter case.
+latter case.  If you want to format a single commit, you can do
+this with "git format-patch -1 <commit>".
 
 By default, each output file is numbered sequentially from 1, and uses the
 first line of the commit message (massaged for pathname safety) as
index 26945593cb1739bb6a6e1e2acc2cd78caab3a102..eb648418eedba4f131aec1f18ccba3bd62f53dc2 100644 (file)
@@ -163,8 +163,8 @@ few exceptions.  Even though...
 `ident`
 ^^^^^^^
 
-When the attribute `ident` is set to a path, git replaces
-`$Id$` in the blob object with `$Id:`, followed by
+When the attribute `ident` is set for a path, git replaces
+`$Id$` in the blob object with `$Id:`, followed by the
 40-character hexadecimal blob object name, followed by a dollar
 sign `$` upon checkout.  Any byte sequence that begins with
 `$Id:` and ends with `$` in the worktree file is replaced
@@ -213,6 +213,9 @@ with `crlf`, and then `ident` and fed to `filter`.
 Generating diff text
 ~~~~~~~~~~~~~~~~~~~~
 
+`diff`
+^^^^^^
+
 The attribute `diff` affects if 'git-diff' generates textual
 patch for the path or just says `Binary files differ`.  It also
 can affect what line is shown on the hunk header `@@ -k,l +n,m @@`
@@ -331,6 +334,9 @@ patterns are available:
 Performing a three-way merge
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+`merge`
+^^^^^^^
+
 The attribute `merge` affects how three versions of a file is
 merged when a file-level merge is necessary during `git merge`,
 and other programs such as `git revert` and `git cherry-pick`.
index d2970f8357505f5973989f2e118b812d3d3dde67..2cdacd94cd1b53536d4726e05a9f9f8620e85ec6 100644 (file)
@@ -37,9 +37,9 @@ of `i18n.commitencoding` in its `encoding` header.  This is to
 help other people who look at them later.  Lack of this header
 implies that the commit log message is encoded in UTF-8.
 
-. 'git-log', 'git-show' and friends looks at the `encoding`
-  header of a commit object, and tries to re-code the log
-  message into UTF-8 unless otherwise specified.  You can
+. 'git-log', 'git-show', 'git-blame' and friends look at the
+  `encoding` header of a commit object, and try to re-code the
+  log message into UTF-8 unless otherwise specified.  You can
   specify the desired output encoding with
   `i18n.logoutputencoding` in `.git/config` file, like this:
 +
index 0ce916a1887b0846bfc5a6e2233242601e0dde79..966276b169d6fb09c571a05257dfbc1fe3bd4f03 100644 (file)
@@ -174,6 +174,10 @@ endif::git-rev-list[]
        Limit the commits output to ones with log message that
        matches the specified pattern (regular expression).
 
+--all-match::
+       Limit the commits output to ones that match all given --grep,
+       --author and --committer instead of ones that match at least one.
+
 -i::
 --regexp-ignore-case::
 
diff --git a/INSTALL b/INSTALL
index a4fd8624bc6550d0553f2271343f4e6b610be103..d1deb0b3c7a9aba961d40fe541490562fee486ac 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -126,8 +126,9 @@ Issues of note:
 
        http://www.kernel.org/pub/software/scm/git/docs/
 
-   There are also "make quick-install-doc" and "make quick-install-html"
-   which install preformatted man pages and html documentation.
+   There are also "make quick-install-doc", "make quick-install-man"
+   and "make quick-install-html" which install preformatted man pages
+   and html documentation.
    This does not require asciidoc/xmlto, but it only works from within
    a cloned checkout of git.git with these two extra branches, and will
    not work for the maintainer for obvious chicken-and-egg reasons.
index d6f3695c97d5c36fe3b99dfc056173b6d785cd4b..220de39914c4df3abf394ad052ebb1fd6f62d3f7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -640,8 +640,6 @@ ifeq ($(uname_S),Darwin)
        endif
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
-       COMPAT_CFLAGS += -Icompat/regex
-       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -692,8 +690,6 @@ ifeq ($(uname_S),FreeBSD)
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
-       COMPAT_CFLAGS += -Icompat/regex
-       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
@@ -720,8 +716,6 @@ ifeq ($(uname_S),AIX)
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
-       COMPAT_CFLAGS += -Icompat/regex
-       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
@@ -1411,6 +1405,9 @@ endif
 install-doc:
        $(MAKE) -C Documentation install
 
+install-man:
+       $(MAKE) -C Documentation install-man
+
 install-html:
        $(MAKE) -C Documentation install-html
 
@@ -1420,6 +1417,9 @@ install-info:
 quick-install-doc:
        $(MAKE) -C Documentation quick-install
 
+quick-install-man:
+       $(MAKE) -C Documentation quick-install-man
+
 quick-install-html:
        $(MAKE) -C Documentation quick-install-html
 
index 48cc0c175d52446266658d785de0311daaaf4776..2457e71fc03fa9f6a37805617a8a9f4771ff1670 100644 (file)
@@ -1431,7 +1431,7 @@ static void get_commit_info(struct commit *commit,
                            int detailed)
 {
        int len;
-       char *tmp, *endp;
+       char *tmp, *endp, *reencoded, *message;
        static char author_buf[1024];
        static char committer_buf[1024];
        static char summary_buf[1024];
@@ -1449,24 +1449,29 @@ static void get_commit_info(struct commit *commit,
                        die("Cannot read commit %s",
                            sha1_to_hex(commit->object.sha1));
        }
+       reencoded = reencode_commit_message(commit, NULL);
+       message   = reencoded ? reencoded : commit->buffer;
        ret->author = author_buf;
-       get_ac_line(commit->buffer, "\nauthor ",
+       get_ac_line(message, "\nauthor ",
                    sizeof(author_buf), author_buf, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
 
-       if (!detailed)
+       if (!detailed) {
+               free(reencoded);
                return;
+       }
 
        ret->committer = committer_buf;
-       get_ac_line(commit->buffer, "\ncommitter ",
+       get_ac_line(message, "\ncommitter ",
                    sizeof(committer_buf), committer_buf, &ret->committer_mail,
                    &ret->committer_time, &ret->committer_tz);
 
        ret->summary = summary_buf;
-       tmp = strstr(commit->buffer, "\n\n");
+       tmp = strstr(message, "\n\n");
        if (!tmp) {
        error_out:
                sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+               free(reencoded);
                return;
        }
        tmp += 2;
@@ -1478,6 +1483,7 @@ static void get_commit_info(struct commit *commit,
                goto error_out;
        memcpy(summary_buf, tmp, len);
        summary_buf[len] = 0;
+       free(reencoded);
 }
 
 /*
index 8d634ff571ce34ce21a5519628d6f66a7e52aa93..2b3613fea2d7799b7d20e9e30da20527d811ed64 100644 (file)
@@ -160,7 +160,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                        continue;
                }
 
-               if (delete_ref(name, sha1)) {
+               if (delete_ref(name, sha1, 0)) {
                        error("Error deleting %sbranch '%s'", remote,
                               argv[i]);
                        ret = 1;
index 4ba27024c53ca1b67e9b6d6f4a62aa7548b7b96d..0d534bc023a1a3367bdf19f6450ce17e627959f0 100644 (file)
@@ -40,6 +40,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "cache-tree.h"
+#include "parse-options.h"
 
 #define CHECKOUT_ALL 4
 static int line_termination = '\n';
@@ -153,11 +154,58 @@ static void checkout_all(const char *prefix, int prefix_length)
                exit(128);
 }
 
-static const char checkout_cache_usage[] =
-"git checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
+static const char * const builtin_checkout_index_usage[] = {
+       "git checkout-index [options] [--] <file>...",
+       NULL
+};
 
 static struct lock_file lock_file;
 
+static int option_parse_u(const struct option *opt,
+                             const char *arg, int unset)
+{
+       int *newfd = opt->value;
+
+       state.refresh_cache = 1;
+       if (*newfd < 0)
+               *newfd = hold_locked_index(&lock_file, 1);
+       return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               line_termination = '\n';
+       else
+               line_termination = 0;
+       return 0;
+}
+
+static int option_parse_prefix(const struct option *opt,
+                              const char *arg, int unset)
+{
+       state.base_dir = arg;
+       state.base_dir_len = strlen(arg);
+       return 0;
+}
+
+static int option_parse_stage(const struct option *opt,
+                             const char *arg, int unset)
+{
+       if (!strcmp(arg, "all")) {
+               to_tempfile = 1;
+               checkout_stage = CHECKOUT_ALL;
+       } else {
+               int ch = arg[0];
+               if ('1' <= ch && ch <= '3')
+                       checkout_stage = arg[0] - '0';
+               else
+                       die("stage should be between 1 and 3 or all");
+       }
+       return 0;
+}
+
 int cmd_checkout_index(int argc, const char **argv, const char *prefix)
 {
        int i;
@@ -165,6 +213,33 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        int all = 0;
        int read_from_stdin = 0;
        int prefix_length;
+       int force = 0, quiet = 0, not_new = 0;
+       struct option builtin_checkout_index_options[] = {
+               OPT_BOOLEAN('a', "all", &all,
+                       "checks out all files in the index"),
+               OPT_BOOLEAN('f', "force", &force,
+                       "forces overwrite of existing files"),
+               OPT__QUIET(&quiet),
+               OPT_BOOLEAN('n', "no-create", &not_new,
+                       "don't checkout new files"),
+               { OPTION_CALLBACK, 'u', "index", &newfd, NULL,
+                       "update stat information in the index file",
+                       PARSE_OPT_NOARG, option_parse_u },
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_BOOLEAN(0, "stdin", &read_from_stdin,
+                       "read list of paths from the standard input"),
+               OPT_BOOLEAN(0, "temp", &to_tempfile,
+                       "write the content to temporary files"),
+               OPT_CALLBACK(0, "prefix", NULL, "string",
+                       "when creating files, prepend <string>",
+                       option_parse_prefix),
+               OPT_CALLBACK(0, "stage", NULL, NULL,
+                       "copy out the files from named stage",
+                       option_parse_stage),
+               OPT_END()
+       };
 
        git_config(git_default_config, NULL);
        state.base_dir = "";
@@ -174,72 +249,11 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
                die("invalid cache");
        }
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) {
-                       all = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) {
-                       state.force = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
-                       state.quiet = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) {
-                       state.not_new = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
-                       state.refresh_cache = 1;
-                       if (newfd < 0)
-                               newfd = hold_locked_index(&lock_file, 1);
-                       continue;
-               }
-               if (!strcmp(arg, "-z")) {
-                       line_termination = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--stdin")) {
-                       if (i != argc - 1)
-                               die("--stdin must be at the end");
-                       read_from_stdin = 1;
-                       i++; /* do not consider arg as a file name */
-                       break;
-               }
-               if (!strcmp(arg, "--temp")) {
-                       to_tempfile = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--prefix=")) {
-                       state.base_dir = arg+9;
-                       state.base_dir_len = strlen(state.base_dir);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--stage=")) {
-                       if (!strcmp(arg + 8, "all")) {
-                               to_tempfile = 1;
-                               checkout_stage = CHECKOUT_ALL;
-                       } else {
-                               int ch = arg[8];
-                               if ('1' <= ch && ch <= '3')
-                                       checkout_stage = arg[8] - '0';
-                               else
-                                       die("stage should be between 1 and 3 or all");
-                       }
-                       continue;
-               }
-               if (arg[0] == '-')
-                       usage(checkout_cache_usage);
-               break;
-       }
+       argc = parse_options(argc, argv, builtin_checkout_index_options,
+                       builtin_checkout_index_usage, 0);
+       state.force = force;
+       state.quiet = quiet;
+       state.not_new = not_new;
 
        if (state.base_dir_len || to_tempfile) {
                /* when --prefix is specified we do not
@@ -253,7 +267,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        }
 
        /* Check out named files first */
-       for ( ; i < argc; i++) {
+       for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
                const char *p;
 
index 59c30d1caa37416041177ff4aaf01b67f4e8add4..15b80db5a1a43ae2ae44375c2cb27978c2e70a71 100644 (file)
@@ -1375,7 +1375,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
        array = xcalloc(window, sizeof(struct unpacked));
 
        for (;;) {
-               struct object_entry *entry = *list++;
+               struct object_entry *entry;
                struct unpacked *n = array + idx;
                int j, max_depth, best_base = -1;
 
@@ -1384,6 +1384,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                        progress_unlock();
                        break;
                }
+               entry = *list++;
                (*list_size)--;
                if (!entry->preferred_base) {
                        (*processed)++;
index 9f60f31c2bb7da7095de8d692d215c485ab5da0c..7f9f134806766244bf1eb3de2de3a823a62b180a 100644 (file)
@@ -11,6 +11,7 @@
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
+static int deny_deletes = 0;
 static int deny_non_fast_forwards = 0;
 static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
@@ -23,6 +24,11 @@ static int capabilities_sent;
 
 static int receive_pack_config(const char *var, const char *value, void *cb)
 {
+       if (strcmp(var, "receive.denydeletes") == 0) {
+               deny_deletes = git_config_bool(var, value);
+               return 0;
+       }
+
        if (strcmp(var, "receive.denynonfastforwards") == 0) {
                deny_non_fast_forwards = git_config_bool(var, value);
                return 0;
@@ -185,6 +191,12 @@ 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 (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
            !prefixcmp(name, "refs/heads/")) {
@@ -224,7 +236,7 @@ static const char *update(struct command *cmd)
                        warning ("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
                }
-               if (delete_ref(name, old_sha1)) {
+               if (delete_ref(name, old_sha1, 0)) {
                        error("failed to delete %s", name);
                        return "failed to delete";
                }
index df2be068b69b6d926d4980e6e9eada05d92643e1..e396a3ac9055aa91d0f808abb196fb6ea9a99a29 100644 (file)
@@ -337,7 +337,7 @@ static int remove_branches(struct string_list *branches)
                const char *refname = item->string;
                unsigned char *sha1 = item->util;
 
-               if (delete_ref(refname, sha1))
+               if (delete_ref(refname, sha1, 0))
                        result |= error("Could not remove branch %s", refname);
        }
        return result;
@@ -565,7 +565,7 @@ static int prune(int argc, const char **argv)
                        const char *refname = states.stale.items[i].util;
 
                        if (!dry_run)
-                               result |= delete_ref(refname, NULL);
+                               result |= delete_ref(refname, NULL, 0);
 
                        printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                               abbrev_ref(refname, "refs/remotes/"));
index 16e6bb20f1af6a7a684a747b3c0cc26ceabe8ce7..9514b77f8c0b4e8a576e888df905b501e198df24 100644 (file)
@@ -279,7 +279,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
        }
        else if (old_orig)
-               delete_ref("ORIG_HEAD", old_orig);
+               delete_ref("ORIG_HEAD", old_orig, 0);
        prepend_reflog_action("updating HEAD", msg, sizeof(msg));
        update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
 
index 7e50c2aa542772b2bac7c599df8865c3dbae5bcd..4038b4118da087069db82bfcf6e0dd298aec9036 100644 (file)
@@ -251,7 +251,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        int i, index_fd, clean;
        char *oneline, *reencoded_message = NULL;
        const char *message, *encoding;
-       const char *defmsg = git_pathdup("MERGE_MSG");
+       char *defmsg = git_pathdup("MERGE_MSG");
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
        static struct lock_file index_lock;
@@ -432,6 +432,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                return execv_git_cmd(args);
        }
        free(reencoded_message);
+       free(defmsg);
 
        return 0;
 }
index 910db92b62eb6dd91a4002b2643fef4a76ec8f83..298bd719240ba503a3128486883d33df011765b1 100644 (file)
@@ -140,7 +140,13 @@ static struct ref *remote_refs, **remote_tail;
 static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct ref *ref;
-       int len = strlen(refname) + 1;
+       int len;
+
+       /* we already know it starts with refs/ to get here */
+       if (check_ref_format(refname + 5))
+               return 0;
+
+       len = strlen(refname) + 1;
        ref = xcalloc(1, sizeof(*ref) + len);
        hashcpy(ref->new_sha1, sha1);
        memcpy(ref->name, refname, len);
@@ -234,7 +240,7 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
                if (args.verbose)
                        fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
                if (ref->deletion) {
-                       delete_ref(rs.dst, NULL);
+                       delete_ref(rs.dst, NULL, 0);
                } else
                        update_ref("update by push", rs.dst,
                                        ref->new_sha1, NULL, 0, 0);
index efd77231ffb6f2574d130a612a1c38dfe646fdf8..84db156e63681de6322019b9b9ffa15371c443b1 100644 (file)
@@ -125,7 +125,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
 static int delete_tag(const char *name, const char *ref,
                                const unsigned char *sha1)
 {
-       if (delete_ref(ref, sha1))
+       if (delete_ref(ref, sha1, 0))
                return 1;
        printf("Deleted tag '%s'\n", name);
        return 0;
index 56a0b1b39cf4c4fc51dbbff256240655bc36a038..378dc1b7a6bb4d56d301a34a4d44dae2f9a37e44 100644 (file)
@@ -13,7 +13,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
        const char *refname, *oldval, *msg=NULL;
        unsigned char sha1[20], oldsha1[20];
-       int delete = 0, no_deref = 0;
+       int delete = 0, no_deref = 0, flags = 0;
        struct option options[] = {
                OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
                OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
@@ -47,9 +47,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        if (oldval && *oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
+       if (no_deref)
+               flags = REF_NODEREF;
        if (delete)
-               return delete_ref(refname, oldval ? oldsha1 : NULL);
+               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
        else
                return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
+                                 flags, DIE_ON_ERR);
 }
diff --git a/cache.h b/cache.h
index 24d0d44f77123e2c869097191bfc7310621326e5..eda7028992421e3946d93179a0dd6539f43967bb 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -434,7 +434,7 @@ extern int commit_locked_index(struct lock_file *);
 extern void set_alternate_index_output(const char *);
 extern int close_lock_file(struct lock_file *);
 extern void rollback_lock_file(struct lock_file *);
-extern int delete_ref(const char *, const unsigned char *sha1);
+extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
 
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
index 4c05864fb426d61a652f7df12c13315665972846..3a7b06a828930ca23d0fb045feab993a1452b2d3 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -65,6 +65,8 @@ enum cmit_fmt {
 
 extern int non_ascii(int);
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
+extern char *reencode_commit_message(const struct commit *commit,
+                                    const char **encoding_p);
 extern void get_commit_format(const char *arg, struct rev_info *);
 extern void format_commit_message(const struct commit *commit,
                                  const void *format, struct strbuf *sb,
index f1967532bae87cbe3627b065083cc56ca0b64550..ebac1483929c798905e6558e0013e3de3d6abeb2 100644 (file)
@@ -91,26 +91,32 @@ static int cygwin_stat(const char *path, struct stat *buf)
  * functions should be used. The choice is determined by core.ignorecygwinfstricks.
  * Reading this option is not always possible immediately as git_dir may be
  * not be set yet. So until it is set, use cygwin lstat/stat functions.
- * However, if the trust_executable_bit is set, we must use the Cygwin posix
+ * However, if core.filemode is set, we must use the Cygwin posix
  * stat/lstat as the Windows stat fuctions do not determine posix filemode.
+ *
+ * Note that git_cygwin_config() does NOT call git_default_config() and this
+ * is deliberate.  Many commands read from config to establish initial
+ * values in variables and later tweak them from elsewhere (e.g. command line).
+ * init_stat() is called lazily on demand, typically much late in the program,
+ * and calling git_default_config() from here would break such variables.
  */
 static int native_stat = 1;
-extern int trust_executable_bit;
+static int core_filemode;
 
 static int git_cygwin_config(const char *var, const char *value, void *cb)
 {
-       if (!strcmp(var, "core.ignorecygwinfstricks")) {
+       if (!strcmp(var, "core.ignorecygwinfstricks"))
                native_stat = git_config_bool(var, value);
-               return 0;
-       }
-       return git_default_config(var, value, cb);
+       else if (!strcmp(var, "core.filemode"))
+               core_filemode = git_config_bool(var, value);
+       return 0;
 }
 
 static int init_stat(void)
 {
        if (have_git_dir()) {
                git_config(git_cygwin_config, NULL);
-               if (!trust_executable_bit && native_stat) {
+               if (!core_filemode && native_stat) {
                        cygwin_stat_fn = cygwin_stat;
                        cygwin_lstat_fn = cygwin_lstat;
                } else {
index 0c50d0a26a493f4505eee16812d14c5d7d1b7392..584e04c217da4ea8943e33c77fea56ce64547ed1 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -70,6 +70,9 @@ struct ref **get_remote_heads(int in, struct ref **list,
                if (buffer[len-1] == '\n')
                        buffer[--len] = 0;
 
+               if (len > 4 && !prefixcmp(buffer, "ERR "))
+                       die("remote error: %s", buffer + 4);
+
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
                        die("protocol error: expected sha/ref, got '%s'", buffer);
                name = buffer + 41;
index eebe73409bebb8e8c2bb448f1612117cabf8d2ad..de193ba7c1caf69367410d763f3541555a64746f 100755 (executable)
@@ -1398,6 +1398,8 @@ _git_shortlog ()
 
 _git_show ()
 {
+       __git_has_doubledash && return
+
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --pretty=*)
index 41368950d6b29121089ee9239b8e07ece209a31e..28a3c0e46ecf9951f3f42a025a288a65c70e0424 100644 (file)
 # hooks.emailprefix
 #   All emails have their subjects prefixed with this prefix, or "[SCM]"
 #   if emailprefix is unset, to aid filtering
+# hooks.showrev
+#   The shell command used to format each revision in the email, with
+#   "%s" replaced with the commit id.  Defaults to "git rev-list -1
+#   --pretty %s", displaying the commit id, author, date and log
+#   message.  To list full patches separated by a blank line, you
+#   could set this to "git show -C %s; echo".
 #
 # Notes
 # -----
@@ -224,13 +230,7 @@ generate_create_branch_email()
        echo ""
 
        echo $LOGBEGIN
-       # This shows all log entries that are not already covered by
-       # another ref - i.e. commits that are now accessible from this
-       # ref that were previously not accessible
-       # (see generate_update_branch_email for the explanation of this
-       # command)
-       git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
-       git rev-list --pretty --stdin $newrev
+       show_new_revisions
        echo $LOGEND
 }
 
@@ -390,8 +390,7 @@ generate_update_branch_email()
 
                echo ""
                echo $LOGBEGIN
-               git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
-               git rev-list --pretty --stdin $oldrev..$newrev
+               show_new_revisions
 
                # XXX: Need a way of detecting whether git rev-list actually
                # outputted anything, so that we can issue a "no new
@@ -591,6 +590,45 @@ generate_delete_general_email()
        echo $LOGEND
 }
 
+
+# --------------- Miscellaneous utilities
+
+#
+# Show new revisions as the user would like to see them in the email.
+#
+show_new_revisions()
+{
+       # This shows all log entries that are not already covered by
+       # another ref - i.e. commits that are now accessible from this
+       # ref that were previously not accessible
+       # (see generate_update_branch_email for the explanation of this
+       # command)
+
+       # Revision range passed to rev-list differs for new vs. updated
+       # branches.
+       if [ "$change_type" = create ]
+       then
+               # Show all revisions exclusive to this (new) branch.
+               revspec=$newrev
+       else
+               # Branch update; show revisions not part of $oldrev.
+               revspec=$oldrev..$newrev
+       fi
+
+       git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+       if [ -z "$custom_showrev" ]
+       then
+               git rev-list --pretty --stdin $revspec
+       else
+               git rev-list --stdin $revspec |
+               while read onerev
+               do
+                       eval $(printf "$custom_showrev" $onerev)
+               done
+       fi
+}
+
+
 send_mail()
 {
        if [ -n "$envelopesender" ]; then
@@ -627,6 +665,7 @@ recipients=$(git config hooks.mailinglist)
 announcerecipients=$(git config hooks.announcelist)
 envelopesender=$(git config hooks.envelopesender)
 emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
+custom_showrev=$(git config hooks.showrev)
 
 # --- Main loop
 # Allow dual mode: run from the command line just like the update hook, or
index 1b2ebb40014d820fe4fb679509ab694d453be7b4..168a95b541c2d6a4679115ebc9f30b1016645b19 100644 (file)
@@ -493,7 +493,7 @@ void diffcore_rename(struct diff_options *options)
        if ((num_create > rename_limit && num_src > rename_limit) ||
            (num_create * num_src > rename_limit * rename_limit)) {
                if (options->warn_on_too_large_rename)
-                       warning("too many files, skipping inexact rename detection");
+                       warning("too many files (created: %d deleted: %d), skipping inexact rename detection", num_create, num_src);
                goto cleanup;
        }
 
index 713cca785c4b986b5f667086ad43c324f248929c..5b634585e8970de81067b9807d8813c083b86b0a 100644 (file)
@@ -62,7 +62,7 @@ struct diff_filepair {
        struct diff_filespec *one;
        struct diff_filespec *two;
        unsigned short int score;
-       char status; /* M C R N D U (see Documentation/diff-format.txt) */
+       char status; /* M C R A D U etc. (see Documentation/diff-format.txt or DIFF_STATUS_* in diff.h) */
        unsigned broken_pair : 1;
        unsigned renamed_pair : 1;
        unsigned is_unmerged : 1;
index 55765c8a3aa6b3702b230e8efe3c2ab47a6e73e5..3ad8a21b30128ce21b6b6c28094d9af5e5866faf 100644 (file)
@@ -285,6 +285,7 @@ all:: $(GITGUI_MAIN) lib/tclIndex $(ALL_MSGFILES)
 install: all
        $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
        $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(QUIET)$(INSTALL_X0)git-gui--askpass $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
        $(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
@@ -302,6 +303,7 @@ endif
 uninstall:
        $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
+       $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askpass $(REMOVE_F1)
        $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
        $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1)
diff --git a/git-gui/git-gui--askpass b/git-gui/git-gui--askpass
new file mode 100755 (executable)
index 0000000..12e117e
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+# This is a trivial implementation of an SSH_ASKPASS handler.
+# Git-gui uses this script if none are already configured.
+
+set answer {}
+set yesno  0
+set rc     255
+
+if {$argc < 1} {
+       set prompt "Enter your OpenSSH passphrase:"
+} else {
+       set prompt [join $argv " "]
+       if {[regexp -nocase {\(yes\/no\)\?\s*$} $prompt]} {
+               set yesno 1
+       }
+}
+
+message .m -text $prompt -justify center -aspect 4000
+pack .m -side top -fill x -padx 20 -pady 20 -expand 1
+
+entry .e -textvariable answer -width 50
+pack .e -side top -fill x -padx 10 -pady 10
+
+if {!$yesno} {
+       .e configure -show "*"
+}
+
+frame .b
+button .b.ok     -text OK     -command finish
+button .b.cancel -text Cancel -command {destroy .}
+
+pack .b.ok -side left -expand 1
+pack .b.cancel -side right -expand 1
+pack .b -side bottom -fill x -padx 10 -pady 10
+
+bind . <Visibility> {focus -force .e}
+bind . <Key-Return> finish
+bind . <Key-Escape> {destroy .}
+bind . <Destroy>    {exit $rc}
+
+proc finish {} {
+       if {$::yesno} {
+               if {$::answer ne "yes" && $::answer ne "no"} {
+                       tk_messageBox -icon error -title "Error" -type ok \
+                               -message "Only 'yes' or 'no' input allowed."
+                       return
+               }
+       }
+
+       set ::rc 0
+       puts $::answer
+       destroy .
+}
+
+wm title . "OpenSSH"
+tk::PlaceWindow .
index 4085e8fea59f2b819812f20185df89379d851c0b..12b496bec9233a86da18a45073d964bd559f059c 100755 (executable)
@@ -591,6 +591,12 @@ bind . <Visibility> {
 
 if {[is_Windows]} {
        wm iconbitmap . -default $oguilib/git-gui.ico
+       set ::tk::AlwaysShowSelection 1
+
+       # Spoof an X11 display for SSH
+       if {![info exists env(DISPLAY)]} {
+               set env(DISPLAY) :9999
+       }
 }
 
 ######################################################################
@@ -995,6 +1001,7 @@ citool {
 ##
 ## repository setup
 
+set picked 0
 if {[catch {
                set _gitdir $env(GIT_DIR)
                set _prefix {}
@@ -1006,6 +1013,7 @@ if {[catch {
        load_config 1
        apply_config
        choose_repository::pick
+       set picked 1
 }
 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
        catch {set _gitdir [exec cygpath --windows $_gitdir]}
@@ -1065,6 +1073,15 @@ set selected_commit_type new
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
 
+set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
+
+######################################################################
+
+# Suggest our implementation of askpass, if none is set
+if {![info exists env(SSH_ASKPASS)]} {
+       set env(SSH_ASKPASS) [gitexec git-gui--askpass]
+}
+
 ######################################################################
 ##
 ## task management
@@ -1869,6 +1886,19 @@ proc do_gitk {revs} {
        }
 }
 
+proc do_explore {} {
+       set explorer {}
+       if {[is_Cygwin] || [is_Windows]} {
+               set explorer "explorer.exe"
+       } elseif {[is_MacOSX]} {
+               set explorer "open"
+       } else {
+               # freedesktop.org-conforming system is our best shot
+               set explorer "xdg-open"
+       }
+       eval exec $explorer [file dirname [gitdir]] &
+}
+
 set is_quitting 0
 set ret_code    1
 
@@ -2090,7 +2120,9 @@ proc toggle_or_diff {w x y} {
        if {$col == 0 && $y > 1} {
                # Conflicts need special handling
                if {[string first {U} $state] >= 0} {
-                       merge_stage_workdir $path $w $lno
+                       # $w must always be $ui_workdir, but...
+                       if {$w ne $ui_workdir} { set lno {} }
+                       merge_stage_workdir $path $lno
                        return
                }
 
@@ -2218,6 +2250,11 @@ if {[is_enabled transport]} {
 #
 menu .mbar.repository
 
+.mbar.repository add command \
+       -label [mc "Explore Working Copy"] \
+       -command {do_explore}
+.mbar.repository add separator
+
 .mbar.repository add command \
        -label [mc "Browse Current Branch's Files"] \
        -command {browser::new $current_branch}
@@ -2413,7 +2450,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 
        .mbar.commit add separator
 
-       if {![is_enabled nocommit]} {
+       if {![is_enabled nocommitmsg]} {
                .mbar.commit add command -label [mc "Sign Off"] \
                        -command do_signoff \
                        -accelerator $M1T-S
@@ -2446,12 +2483,16 @@ if {[is_enabled branch]} {
 if {[is_enabled transport]} {
        menu .mbar.remote
 
+       .mbar.remote add command \
+               -label [mc "Add..."] \
+               -command remote_add::dialog \
+               -accelerator $M1T-A
        .mbar.remote add command \
                -label [mc "Push..."] \
                -command do_push_anywhere \
                -accelerator $M1T-P
        .mbar.remote add command \
-               -label [mc "Delete..."] \
+               -label [mc "Delete Branch..."] \
                -command remote_branch_delete::dialog
 }
 
@@ -2487,8 +2528,7 @@ if {![is_MacOSX]} {
                -command do_about
 }
 
-set browser {}
-catch {set browser $repo_config(instaweb.browser)}
+
 set doc_path [file dirname [gitexec]]
 set doc_path [file join $doc_path Documentation index.html]
 
@@ -2496,34 +2536,23 @@ if {[is_Cygwin]} {
        set doc_path [exec cygpath --mixed $doc_path]
 }
 
-if {$browser eq {}} {
-       if {[is_MacOSX]} {
-               set browser open
-       } elseif {[is_Cygwin]} {
-               set program_files [file dirname [exec cygpath --windir]]
-               set program_files [file join $program_files {Program Files}]
-               set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
-               set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
-               if {[file exists $firefox]} {
-                       set browser $firefox
-               } elseif {[file exists $ie]} {
-                       set browser $ie
-               }
-               unset program_files firefox ie
-       }
-}
-
 if {[file isfile $doc_path]} {
        set doc_url "file:$doc_path"
 } else {
        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
 }
 
-if {$browser ne {}} {
-       .mbar.help add command -label [mc "Online Documentation"] \
-               -command [list exec $browser $doc_url &]
+proc start_browser {url} {
+       git "web--browse" $url
 }
-unset browser doc_path doc_url
+
+.mbar.help add command -label [mc "Online Documentation"] \
+       -command [list start_browser $doc_url]
+
+.mbar.help add command -label [mc "Show SSH Key"] \
+       -command do_ssh_key
+
+unset doc_path doc_url
 
 # -- Standard bindings
 #
@@ -2743,7 +2772,7 @@ pack .vpane.lower.commarea.buttons.incall -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.incall conf -state}
 
-if {![is_enabled nocommit]} {
+if {![is_enabled nocommitmsg]} {
        button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
                -command do_signoff
        pack .vpane.lower.commarea.buttons.signoff -side top -fill x
@@ -3261,8 +3290,7 @@ if {[is_enabled transport]} {
        load_all_remotes
 
        set n [.mbar.remote index end]
-       populate_push_menu
-       populate_fetch_menu
+       populate_remotes_menu
        set n [expr {[.mbar.remote index end] - $n}]
        if {$n > 0} {
                if {[.mbar.remote type 0] eq "tearoff"} { incr n }
@@ -3369,3 +3397,6 @@ if {[is_enabled multicommit]} {
 if {[is_enabled retcode]} {
        bind . <Destroy> {+terminate_me %W}
 }
+if {$picked && [is_config_true gui.autoexplore]} {
+       do_explore
+}
index eb61374d2db79a1199d455dfdda85e2d063bf1f6..765d08c004b68254713e2524b53d6df0bdc0c007 100644 (file)
@@ -21,9 +21,11 @@ field w_amov     ; # text column: annotations + move tracking
 field w_asim     ; # text column: annotations (simple computation)
 field w_file     ; # text column: actual file data
 field w_cviewer  ; # pane showing commit message
+field finder     ; # find mini-dialog frame
 field status     ; # status mega-widget instance
 field old_height ; # last known height of $w.file_pane
 
+
 # Tk UI colors
 #
 variable active_color #c0edc5
@@ -59,7 +61,7 @@ field tooltip_timer     {} ; # Current timer event for our tooltip
 field tooltip_commit    {} ; # Commit(s) in tooltip
 
 constructor new {i_commit i_path i_jump} {
-       global cursor_ptr
+       global cursor_ptr M1B M1T have_tk85
        variable active_color
        variable group_colors
 
@@ -69,6 +71,8 @@ constructor new {i_commit i_path i_jump} {
        make_toplevel top w
        wm title $top [append "[appname] ([reponame]): " [mc "File Viewer"]]
 
+       set font_w [font measure font_diff "0"]
+
        frame $w.header -background gold
        label $w.header.commit_l \
                -text [mc "Commit:"] \
@@ -114,9 +118,9 @@ constructor new {i_commit i_path i_jump} {
        pack $w_path -fill x -side right
        pack $w.header.path_l -side right
 
-       panedwindow $w.file_pane -orient vertical
-       frame $w.file_pane.out
-       frame $w.file_pane.cm
+       panedwindow $w.file_pane -orient vertical -borderwidth 0 -sashwidth 3
+       frame $w.file_pane.out -relief flat -borderwidth 1
+       frame $w.file_pane.cm -relief sunken -borderwidth 1
        $w.file_pane add $w.file_pane.out \
                -sticky nsew \
                -minsize 100 \
@@ -197,6 +201,11 @@ constructor new {i_commit i_path i_jump} {
                -width 80 \
                -xscrollcommand [list $w.file_pane.out.sbx set] \
                -font font_diff
+       if {$have_tk85} {
+               $w_file configure -inactiveselectbackground darkblue
+       }
+       $w_file tag conf found \
+               -background yellow
 
        set w_columns [list $w_amov $w_asim $w_line $w_file]
 
@@ -217,6 +226,11 @@ constructor new {i_commit i_path i_jump} {
                -weight 1
        grid rowconfigure $w.file_pane.out 0 -weight 1
 
+       set finder [::searchbar::new \
+               $w.file_pane.out.ff $w_file \
+               -column [expr {[llength $w_columns] - 1}] \
+               ]
+
        set w_cviewer $w.file_pane.cm.t
        text $w_cviewer \
                -background white \
@@ -257,6 +271,10 @@ constructor new {i_commit i_path i_jump} {
                -label [mc "Copy Commit"] \
                -command [cb _copycommit]
        $w.ctxm add separator
+       $w.ctxm add command \
+               -label [mc "Find Text..."] \
+               -accelerator F7 \
+               -command [list searchbar::show $finder]
        menu $w.ctxm.enc
        build_encoding_menu $w.ctxm.enc [cb _setencoding]
        $w.ctxm add cascade \
@@ -278,9 +296,15 @@ constructor new {i_commit i_path i_jump} {
                        $i tag conf color$g -background [lindex $group_colors $g]
                }
 
+               if {$i eq $w_file} {
+                       $w_file tag raise found
+               }
+               $i tag raise sel
+
                $i conf -cursor $cursor_ptr
-               $i conf -yscrollcommand [list many2scrollbar \
-                       $w_columns yview $w.file_pane.out.sby]
+               $i conf -yscrollcommand \
+                       "[list ::searchbar::scrolled $finder]
+                        [list many2scrollbar $w_columns yview $w.file_pane.out.sby]"
                bind $i <Button-1> "
                        [cb _hide_tooltip]
                        [cb _click $i @%x,%y]
@@ -317,6 +341,11 @@ constructor new {i_commit i_path i_jump} {
        bind $w_cviewer <Tab>       "[list focus $w_file];break"
        bind $w_cviewer <Button-1> [list focus $w_cviewer]
        bind $w_file    <Visibility> [list focus $w_file]
+       bind $top       <F7>         [list searchbar::show $finder]
+       bind $top       <Escape>     [list searchbar::hide $finder]
+       bind $top       <F3>         [list searchbar::find_next $finder]
+       bind $top       <Shift-F3>   [list searchbar::find_prev $finder]
+       catch { bind $top <Shift-Key-XF86_Switch_VT_3> [list searchbar::find_prev $finder] }
 
        grid configure $w.header -sticky ew
        grid configure $w.file_pane -sticky nsew
@@ -328,9 +357,14 @@ constructor new {i_commit i_path i_jump} {
 
        set req_w [winfo reqwidth  $top]
        set req_h [winfo reqheight $top]
-       set scr_h [expr {[winfo screenheight $top] - 100}]
-       if {$req_w < 600} {set req_w 600}
+       set scr_w [expr {[winfo screenwidth $top] - 40}]
+       set scr_h [expr {[winfo screenheight $top] - 120}]
+       set opt_w [expr {$font_w * (80 + 5*3 + 3)}]
+       if {$req_w < $opt_w} {set req_w $opt_w}
+       if {$req_w > $scr_w} {set req_w $scr_w}
+       set opt_h [expr {$req_w*4/3}]
        if {$req_h < $scr_h} {set req_h $scr_h}
+       if {$req_h > $opt_h} {set req_h $opt_h}
        set g "${req_w}x${req_h}"
        wm geometry $top $g
        update
@@ -338,16 +372,23 @@ constructor new {i_commit i_path i_jump} {
        set old_height [winfo height $w.file_pane]
        $w.file_pane sash place 0 \
                [lindex [$w.file_pane sash coord 0] 0] \
-               [expr {int($old_height * 0.70)}]
+               [expr {int($old_height * 0.80)}]
        bind $w.file_pane <Configure> \
        "if {{$w.file_pane} eq {%W}} {[cb _resize %h]}"
 
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
-       bind $top <Destroy> [cb _kill]
+       bind $top <Destroy> [cb _handle_destroy %W]
 
        _load $this $i_jump
 }
 
+method _handle_destroy {win} {
+       if {$win eq $w} {
+               _kill $this
+               delete_this
+       }
+}
+
 method _kill {} {
        if {$current_fd ne {}} {
                kill_file_process $current_fd
@@ -866,6 +907,10 @@ method _showcommit {cur_w lno} {
                foreach i $w_columns {
                        $i tag conf g$cmit -background $active_color
                        $i tag raise g$cmit
+                       if {$i eq $w_file} {
+                               $w_file tag raise found
+                       }
+                       $i tag raise sel
                }
 
                set author_name {}
index 318078615862adab052d0d0f637f82176a0a785a..909131689ea837cb67bd587a6acc8b5eb3127c4e 100644 (file)
@@ -381,7 +381,8 @@ method _do_new {} {
        label $w_body.where.l -text [mc "Directory:"]
        entry $w_body.where.t \
                -textvariable @local_path \
-               -font font_diff \
+               -borderwidth 1 \
+               -relief sunken \
                -width 50
        button $w_body.where.b \
                -text [mc "Browse"] \
@@ -463,20 +464,22 @@ method _do_clone {} {
        frame $w_body.args
        pack $args -fill both
 
-       label $args.origin_l -text [mc "URL:"]
+       label $args.origin_l -text [mc "Source Location:"]
        entry $args.origin_t \
                -textvariable @origin_url \
-               -font font_diff \
+               -borderwidth 1 \
+               -relief sunken \
                -width 50
        button $args.origin_b \
                -text [mc "Browse"] \
                -command [cb _open_origin]
        grid $args.origin_l $args.origin_t $args.origin_b -sticky ew
 
-       label $args.where_l -text [mc "Directory:"]
+       label $args.where_l -text [mc "Target Directory:"]
        entry $args.where_t \
                -textvariable @local_path \
-               -font font_diff \
+               -borderwidth 1 \
+               -relief sunken \
                -width 50
        button $args.where_b \
                -text [mc "Browse"] \
@@ -979,7 +982,8 @@ method _do_open {} {
        label $w_body.where.l -text [mc "Repository:"]
        entry $w_body.where.t \
                -textvariable @local_path \
-               -font font_diff \
+               -borderwidth 1 \
+               -relief sunken \
                -width 50
        button $w_body.where.b \
                -text [mc "Browse"] \
index abe502d97104cb62bed308b0f5025c9d014834fb..94ee38cccc1ee9a43f8dd1309353609b283d8af5 100644 (file)
@@ -117,22 +117,22 @@ proc show_unmerged_diff {cont_info} {
        if {$merge_stages(2) eq {}} {
                set is_conflict_diff 1
                lappend current_diff_queue \
-                       [list "LOCAL: deleted\nREMOTE:\n" d======= \
+                       [list [mc "LOCAL: deleted\nREMOTE:\n"] d======= \
                            [list ":1:$current_diff_path" ":3:$current_diff_path"]]
        } elseif {$merge_stages(3) eq {}} {
                set is_conflict_diff 1
                lappend current_diff_queue \
-                       [list "REMOTE: deleted\nLOCAL:\n" d======= \
+                       [list [mc "REMOTE: deleted\nLOCAL:\n"] d======= \
                            [list ":1:$current_diff_path" ":2:$current_diff_path"]]
        } elseif {[lindex $merge_stages(1) 0] eq {120000}
                || [lindex $merge_stages(2) 0] eq {120000}
                || [lindex $merge_stages(3) 0] eq {120000}} {
                set is_conflict_diff 1
                lappend current_diff_queue \
-                       [list "LOCAL:\n" d======= \
+                       [list [mc "LOCAL:\n"] d======= \
                            [list ":1:$current_diff_path" ":2:$current_diff_path"]]
                lappend current_diff_queue \
-                       [list "REMOTE:\n" d======= \
+                       [list [mc "REMOTE:\n"] d======= \
                            [list ":1:$current_diff_path" ":3:$current_diff_path"]]
        } else {
                start_show_diff $cont_info
@@ -164,7 +164,7 @@ proc show_other_diff {path w m cont_info} {
        # - Git won't give us the diff, there's nothing to compare to!
        #
        if {$m eq {_O}} {
-               set max_sz [expr {128 * 1024}]
+               set max_sz 100000
                set type unknown
                if {[catch {
                                set type [file type $path]
@@ -218,17 +218,17 @@ proc show_other_diff {path w m cont_info} {
                                d_@
                } else {
                        if {$sz > $max_sz} {
-                               $ui_diff insert end \
-"* Untracked file is $sz bytes.
-* Showing only first $max_sz bytes.
-" d_@
+                               $ui_diff insert end [mc \
+"* Untracked file is %d bytes.
+* Showing only first %d bytes.
+" $sz $max_sz] d_@
                        }
                        $ui_diff insert end $content
                        if {$sz > $max_sz} {
-                               $ui_diff insert end "
-* Untracked file clipped here by [appname].
+                               $ui_diff insert end [mc "
+* Untracked file clipped here by %s.
 * To see the entire file, use an external editor.
-" d_@
+" [appname]] d_@
                        }
                }
                $ui_diff conf -state disabled
@@ -377,7 +377,6 @@ proc read_diff {fd cont_info} {
                        {+} {
                                if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
                                        set is_conflict_diff 1
-                                       set line [string replace $line 0 0 { }]
                                        set tags d$op
                                } else {
                                        set tags d_+
index b045219a1cca6bf46218bf211c0c88b8d699e144..d33896a0ce26dd34c8024e21c71123e62832b8c0 100644 (file)
@@ -298,11 +298,18 @@ proc add_helper {txt paths} {
        set after {}
        foreach path $paths {
                switch -glob -- [lindex $file_states($path) 0] {
+               _U -
+               U? {
+                       if {$path eq $current_diff_path} {
+                               unlock_index
+                               merge_stage_workdir $path
+                               return
+                       }
+               }
                _O -
                ?M -
                ?D -
-               ?T -
-               U? {
+               ?T {
                        lappend pathList $path
                        if {$path eq $current_diff_path} {
                                set after {reshow_diff;}
index 5c01875b051305b5b40a17ac7a2245f69081f41b..283e4915e928df0c188d8ed2ec49e49eeed3b519 100644 (file)
@@ -40,6 +40,7 @@ The rescan will be automatically started now.
                _O {
                        continue; # and pray it works!
                }
+               _U -
                U? {
                        error_popup [mc "You are in the middle of a conflicted merge.
 
index 6ab5701d937b9a719570907e745609b60d1d56a8..eb2b4b56a4db4c20727432c7a71d5192d580ce9e 100644 (file)
@@ -23,13 +23,14 @@ This operation can be undone only by restarting the merge." \
        }
 }
 
-proc merge_stage_workdir {path w lno} {
+proc merge_stage_workdir {path {lno {}}} {
        global current_diff_path diff_active
+       global current_diff_side ui_workdir
 
        if {$diff_active} return
 
-       if {$path ne $current_diff_path} {
-               show_diff $path $w $lno {} [list do_merge_stage_workdir $path]
+       if {$path ne $current_diff_path || $ui_workdir ne $current_diff_side} {
+               show_diff $path $ui_workdir $lno {} [list do_merge_stage_workdir $path]
        } else {
                do_merge_stage_workdir $path
        }
@@ -375,14 +376,6 @@ proc merge_tool_finish {fd} {
                }
        }
 
-       # Check the modification time of the target file
-       if {!$failed && [file mtime $mtool_target] eq $mtool_mtime} {
-               if {[ask_popup [mc "File %s unchanged, still accept as resolved?" \
-                               [short_path $mtool_target]]] ne {yes}} {
-                       set failed 1
-               }
-       }
-
        # Finish
        if {$failed} {
                file rename -force -- $backup $mtool_target
@@ -395,6 +388,6 @@ proc merge_tool_finish {fd} {
 
                delete_temp_files $mtool_tmpfiles
 
-               merge_add_resolution $mtool_target
+               reshow_diff
        }
 }
index 0e86ddac0981fbb575a7dd5294ddaed29f7c3917..b92b429cf766d525402047175ed0a69af015c682 100644 (file)
@@ -132,91 +132,145 @@ proc load_all_remotes {} {
        set all_remotes [lsort -unique $all_remotes]
 }
 
-proc populate_fetch_menu {} {
-       global all_remotes repo_config
-
+proc add_fetch_entry {r} {
+       global repo_config
        set remote_m .mbar.remote
        set fetch_m $remote_m.fetch
        set prune_m $remote_m.prune
-
-       foreach r $all_remotes {
-               set enable 0
-               if {![catch {set a $repo_config(remote.$r.url)}]} {
-                       if {![catch {set a $repo_config(remote.$r.fetch)}]} {
-                               set enable 1
-                       }
-               } else {
-                       catch {
-                               set fd [open [gitdir remotes $r] r]
-                               while {[gets $fd n] >= 0} {
-                                       if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
-                                               set enable 1
-                                               break
-                                       }
+       set remove_m $remote_m.remove
+       set enable 0
+       if {![catch {set a $repo_config(remote.$r.url)}]} {
+               if {![catch {set a $repo_config(remote.$r.fetch)}]} {
+                       set enable 1
+               }
+       } else {
+               catch {
+                       set fd [open [gitdir remotes $r] r]
+                       while {[gets $fd n] >= 0} {
+                               if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
+                                       set enable 1
+                                       break
                                }
-                               close $fd
                        }
+                       close $fd
                }
+       }
 
-               if {$enable} {
-                       if {![winfo exists $fetch_m]} {
-                               menu $prune_m
-                               $remote_m insert 0 cascade \
-                                       -label [mc "Prune from"] \
-                                       -menu $prune_m
-
-                               menu $fetch_m
-                               $remote_m insert 0 cascade \
-                                       -label [mc "Fetch from"] \
-                                       -menu $fetch_m
-                       }
+       if {$enable} {
+               if {![winfo exists $fetch_m]} {
+                       menu $remove_m
+                       $remote_m insert 0 cascade \
+                               -label [mc "Remove Remote"] \
+                               -menu $remove_m
+
+                       menu $prune_m
+                       $remote_m insert 0 cascade \
+                               -label [mc "Prune from"] \
+                               -menu $prune_m
 
-                       $fetch_m add command \
-                               -label $r \
-                               -command [list fetch_from $r]
-                       $prune_m add command \
-                               -label $r \
-                               -command [list prune_from $r]
+                       menu $fetch_m
+                       $remote_m insert 0 cascade \
+                               -label [mc "Fetch from"] \
+                               -menu $fetch_m
                }
+
+               $fetch_m add command \
+                       -label $r \
+                       -command [list fetch_from $r]
+               $prune_m add command \
+                       -label $r \
+                       -command [list prune_from $r]
+               $remove_m add command \
+                       -label $r \
+                       -command [list remove_remote $r]
        }
 }
 
-proc populate_push_menu {} {
-       global all_remotes repo_config
-
+proc add_push_entry {r} {
+       global repo_config
        set remote_m .mbar.remote
        set push_m $remote_m.push
-
-       foreach r $all_remotes {
-               set enable 0
-               if {![catch {set a $repo_config(remote.$r.url)}]} {
-                       if {![catch {set a $repo_config(remote.$r.push)}]} {
-                               set enable 1
-                       }
-               } else {
-                       catch {
-                               set fd [open [gitdir remotes $r] r]
-                               while {[gets $fd n] >= 0} {
-                                       if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
-                                               set enable 1
-                                               break
-                                       }
+       set enable 0
+       if {![catch {set a $repo_config(remote.$r.url)}]} {
+               if {![catch {set a $repo_config(remote.$r.push)}]} {
+                       set enable 1
+               }
+       } else {
+               catch {
+                       set fd [open [gitdir remotes $r] r]
+                       while {[gets $fd n] >= 0} {
+                               if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
+                                       set enable 1
+                                       break
                                }
-                               close $fd
                        }
+                       close $fd
                }
+       }
 
-               if {$enable} {
-                       if {![winfo exists $push_m]} {
-                               menu $push_m
-                               $remote_m insert 0 cascade \
-                                       -label [mc "Push to"] \
-                                       -menu $push_m
-                       }
-
-                       $push_m add command \
-                               -label $r \
-                               -command [list push_to $r]
+       if {$enable} {
+               if {![winfo exists $push_m]} {
+                       menu $push_m
+                       $remote_m insert 0 cascade \
+                               -label [mc "Push to"] \
+                               -menu $push_m
                }
+
+               $push_m add command \
+                       -label $r \
+                       -command [list push_to $r]
+       }
+}
+
+proc populate_remotes_menu {} {
+       global all_remotes
+
+       foreach r $all_remotes {
+               add_fetch_entry $r
+               add_push_entry $r
+       }
+}
+
+proc add_single_remote {name location} {
+       global all_remotes repo_config
+       lappend all_remotes $name
+
+       git remote add $name $location
+
+       # XXX: Better re-read the config so that we will never get out
+       # of sync with git remote implementation?
+       set repo_config(remote.$name.url) $location
+       set repo_config(remote.$name.fetch) "+refs/heads/*:refs/remotes/$name/*"
+
+       add_fetch_entry $name
+       add_push_entry $name
+}
+
+proc delete_from_menu {menu name} {
+       if {[winfo exists $menu]} {
+               $menu delete $name
        }
 }
+
+proc remove_remote {name} {
+       global all_remotes repo_config
+
+       git remote rm $name
+
+       catch {
+               # Missing values are ok
+               unset repo_config(remote.$name.url)
+               unset repo_config(remote.$name.fetch)
+               unset repo_config(remote.$name.push)
+       }
+
+       set i [lsearch -exact all_remotes $name]
+       lreplace all_remotes $i $i
+
+       set remote_m .mbar.remote
+       delete_from_menu $remote_m.fetch $name
+       delete_from_menu $remote_m.prune $name
+       delete_from_menu $remote_m.remove $name
+       # Not all remotes are in the push menu
+       catch { delete_from_menu $remote_m.push $name }
+}
diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl
new file mode 100644 (file)
index 0000000..fb29422
--- /dev/null
@@ -0,0 +1,191 @@
+# git-gui remote adding support
+# Copyright (C) 2008 Petr Baudis
+
+class remote_add {
+
+field w              ; # widget path
+field w_name         ; # new remote name widget
+field w_loc          ; # new remote location widget
+
+field name         {}; # name of the remote the user has chosen
+field location     {}; # location of the remote the user has chosen
+
+field opt_action fetch; # action to do after registering the remote locally
+
+constructor dialog {} {
+       global repo_config
+
+       make_toplevel top w
+       wm title $top [append "[appname] ([reponame]): " [mc "Add Remote"]]
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header -text [mc "Add New Remote"] -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text [mc Add] \
+               -default active \
+               -command [cb _add]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text [mc Cancel] \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.desc -text [mc "Remote Details"]
+
+       label $w.desc.name_l -text [mc "Name:"]
+       set w_name $w.desc.name_t
+       entry $w_name \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable @name \
+               -validate key \
+               -validatecommand [cb _validate_name %d %S]
+       grid $w.desc.name_l $w_name -sticky we -padx {0 5}
+
+       label $w.desc.loc_l -text [mc "Location:"]
+       set w_loc $w.desc.loc_t
+       entry $w_loc \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable @location
+       grid $w.desc.loc_l $w_loc -sticky we -padx {0 5}
+
+       grid columnconfigure $w.desc 1 -weight 1
+       pack $w.desc -anchor nw -fill x -pady 5 -padx 5
+
+       labelframe $w.action -text [mc "Further Action"]
+
+       radiobutton $w.action.fetch \
+               -text [mc "Fetch Immediately"] \
+               -value fetch \
+               -variable @opt_action
+       pack $w.action.fetch -anchor nw
+
+       radiobutton $w.action.push \
+               -text [mc "Initialize Remote Repository and Push"] \
+               -value push \
+               -variable @opt_action
+       pack $w.action.push -anchor nw
+
+       radiobutton $w.action.none \
+               -text [mc "Do Nothing Else Now"] \
+               -value none \
+               -variable @opt_action
+       pack $w.action.none -anchor nw
+
+       grid columnconfigure $w.action 1 -weight 1
+       pack $w.action -anchor nw -fill x -pady 5 -padx 5
+
+       bind $w <Visibility> [cb _visible]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [cb _add]\;break
+       tkwait window $w
+}
+
+method _add {} {
+       global repo_config env
+       global M1B
+
+       if {$name eq {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message [mc "Please supply a remote name."]
+               focus $w_name
+               return
+       }
+
+       # XXX: We abuse check-ref-format here, but
+       # that should be ok.
+       if {[catch {git check-ref-format "remotes/$name"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message [mc "'%s' is not an acceptable remote name." $name]
+               focus $w_name
+               return
+       }
+
+       if {[catch {add_single_remote $name $location}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message [mc "Failed to add remote '%s' of location '%s'." $name $location]
+               focus $w_name
+               return
+       }
+
+       switch -- $opt_action {
+       fetch {
+               set c [console::new \
+                       [mc "fetch %s" $name] \
+                       [mc "Fetching the %s" $name]]
+               console::exec $c [list git fetch $name]
+       }
+       push {
+               set cmds [list]
+
+               # Parse the location
+               if { [regexp {(?:git\+)?ssh://([^/]+)(/.+)} $location xx host path]
+                    || [regexp {([^:][^:]+):(.+)} $location xx host path]} {
+                       set ssh ssh
+                       if {[info exists env(GIT_SSH)]} {
+                               set ssh $env(GIT_SSH)
+                       }
+                       lappend cmds [list exec $ssh $host mkdir -p $location && git --git-dir=$path init --bare]
+               } elseif { ! [regexp {://} $location xx] } {
+                       lappend cmds [list exec mkdir -p $location]
+                       lappend cmds [list exec git --git-dir=$location init --bare]
+               } else {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message [mc "Do not know how to initialize repository at location '%s'." $location]
+                       destroy $w
+                       return
+               }
+
+               set c [console::new \
+                       [mc "push %s" $name] \
+                       [mc "Setting up the %s (at %s)" $name $location]]
+
+               lappend cmds [list exec git push -v --all $name]
+               console::chain $c $cmds
+       }
+       none {
+       }
+       }
+
+       destroy $w
+}
+
+method _validate_name {d S} {
+       if {$d == 1} {
+               if {[regexp {[~^:?*\[\0- ]} $S]} {
+                       return 0
+               }
+       }
+       return 1
+}
+
+method _visible {} {
+       grab $w
+       $w_name icursor end
+       focus $w_name
+}
+
+}
index c7b81486984d46a9dca59867c406a8e247d76313..89eb0f70f289e48e2b875e2cd49eb026a266ca0e 100644 (file)
@@ -26,12 +26,12 @@ constructor dialog {} {
        global all_remotes M1B
 
        make_toplevel top w
-       wm title $top [append "[appname] ([reponame]): " [mc "Delete Remote Branch"]]
+       wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch Remotely"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Delete Remote Branch"] -font font_uibold
+       label $w.header -text [mc "Delete Branch Remotely"] -font font_uibold
        pack $w.header -side top -fill x
 
        frame $w.buttons
@@ -63,7 +63,7 @@ constructor dialog {} {
                set urltype url
        }
        radiobutton $w.dest.url_r \
-               -text [mc "Arbitrary URL:"] \
+               -text [mc "Arbitrary Location:"] \
                -value url \
                -variable @urltype
        entry $w.dest.url_t \
diff --git a/git-gui/lib/search.tcl b/git-gui/lib/search.tcl
new file mode 100644 (file)
index 0000000..d292f20
--- /dev/null
@@ -0,0 +1,190 @@
+# incremental search panel
+# based on code from gitk, Copyright (C) Paul Mackerras
+
+class searchbar {
+
+field w
+field ctext
+
+field searchstring   {}
+field casesensitive  1
+field searchdirn     -forwards
+
+field smarktop
+field smarkbot
+
+constructor new {i_w i_text args} {
+       set w      $i_w
+       set ctext  $i_text
+
+       frame  $w
+       label  $w.l       -text [mc Find:]
+       button $w.bn      -text [mc Next] -command [cb find_next]
+       button $w.bp      -text [mc Prev] -command [cb find_prev]
+       checkbutton $w.cs -text [mc Case-Sensitive] \
+               -variable ${__this}::casesensitive -command [cb _incrsearch]
+       entry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
+       pack   $w.l   -side left
+       pack   $w.cs  -side right
+       pack   $w.bp  -side right
+       pack   $w.bn  -side right
+       pack   $w.ent -side left -expand 1 -fill x
+
+       eval grid conf $w -sticky we $args
+       grid remove $w
+
+       trace add variable searchstring write [cb _incrsearch_cb]
+       
+       bind $w <Destroy> [cb delete_this]
+       return $this
+}
+
+method show {} {
+       if {![winfo ismapped $w]} {
+               grid $w
+       }
+       focus -force $w.ent
+}
+
+method hide {} {
+       if {[winfo ismapped $w]} {
+               focus $ctext
+               grid remove $w
+       }
+}
+
+method _get_new_anchor {} {
+       # use start of selection if it is visible,
+       # or the bounds of the visible area
+       set top    [$ctext index @0,0]
+       set bottom [$ctext index @0,[winfo height $ctext]]
+       set sel    [$ctext tag ranges sel]
+       if {$sel ne {}} {
+               set spos [lindex $sel 0]
+               if {[lindex $spos 0] >= [lindex $top 0] &&
+                   [lindex $spos 0] <= [lindex $bottom 0]} {
+                       return $spos
+               }
+       }
+       if {$searchdirn eq "-forwards"} {
+               return $top
+       } else {
+               return $bottom
+       }
+}
+
+method _get_wrap_anchor {dir} {
+       if {$dir eq "-forwards"} {
+               return 1.0
+       } else {
+               return end
+       }
+}
+
+method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
+       set cmd [list $ctext search]
+       if {$mlenvar ne {}} {
+               upvar $mlenvar mlen
+               lappend cmd -count mlen
+       }
+       if {!$casesensitive} {
+               lappend cmd -nocase
+       }
+       if {$dir eq {}} {
+               set dir $searchdirn
+       }
+       lappend cmd $dir -- $searchstring
+       if {$endbound ne {}} {
+               set here [eval $cmd [list $start] [list $endbound]]
+       } else {
+               set here [eval $cmd [list $start]]
+               if {$here eq {}} {
+                       set here [eval $cmd [_get_wrap_anchor $this $dir]]
+               }
+       }
+       return $here
+}
+
+method _incrsearch_cb {name ix op} {
+       after idle [cb _incrsearch]
+}
+
+method _incrsearch {} {
+       $ctext tag remove found 1.0 end
+       if {[catch {$ctext index anchor}]} {
+               $ctext mark set anchor [_get_new_anchor $this]
+       }
+       if {$searchstring ne {}} {
+               set here [_do_search $this anchor mlen]
+               if {$here ne {}} {
+                       $ctext see $here
+                       $ctext tag remove sel 1.0 end
+                       $ctext tag add sel $here "$here + $mlen c"
+                       $w.ent configure -background lightgreen
+                       _set_marks $this 1
+               } else {
+                       $w.ent configure -background lightpink
+               }
+       }
+}
+
+method find_prev {} {
+       find_next $this -backwards
+}
+
+method find_next {{dir -forwards}} {
+       focus $w.ent
+       $w.ent icursor end
+       set searchdirn $dir
+       $ctext mark unset anchor
+       if {$searchstring ne {}} {
+               set start [_get_new_anchor $this]
+               if {$dir eq "-forwards"} {
+                       set start "$start + 1c"
+               }
+               set match [_do_search $this $start mlen]
+               $ctext tag remove sel 1.0 end
+               if {$match ne {}} {
+                       $ctext see $match
+                       $ctext tag add sel $match "$match + $mlen c"
+               }
+       }
+}
+
+method _mark_range {first last} {
+       set mend $first.0
+       while {1} {
+               set match [_do_search $this $mend mlen -forwards $last.end]
+               if {$match eq {}} break
+               set mend "$match + $mlen c"
+               $ctext tag add found $match $mend
+       }
+}
+
+method _set_marks {doall} {
+       set topline [lindex [split [$ctext index @0,0] .] 0]
+       set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
+       if {$doall || $botline < $smarktop || $topline > $smarkbot} {
+               # no overlap with previous
+               _mark_range $this $topline $botline
+               set smarktop $topline
+               set smarkbot $botline
+       } else {
+               if {$topline < $smarktop} {
+                       _mark_range $this $topline [expr {$smarktop-1}]
+                       set smarktop $topline
+               }
+               if {$botline > $smarkbot} {
+                       _mark_range $this [expr {$smarkbot+1}] $botline
+                       set smarkbot $botline
+               }
+       }
+}
+
+method scrolled {} {
+       if {$searchstring ne {}} {
+               after idle [cb _set_marks 0]
+       }
+}
+
+}
\ No newline at end of file
index 78f344f08f34842c134b6b0e22b6eb8c49b1dbbd..e6120303e940ee6c849bf706415c1b450e68b57f 100644 (file)
@@ -80,7 +80,7 @@ method _connect {pipe_fd} {
                error_popup [strcat [mc "Unrecognized spell checker"] ":\n\n$s_version"]
                return
        }
-       set s_version [string range $s_version 5 end]
+       set s_version [string range [string trim $s_version] 5 end]
        regexp \
                {International Ispell Version .* \(but really (Aspell .*?)\)$} \
                $s_version _junk s_version
@@ -314,6 +314,7 @@ method _run {} {
 method _read {} {
        while {[gets $s_fd line] >= 0} {
                set lineno [lindex $s_pending 0 0]
+               set line [string trim $line]
 
                if {$s_clear} {
                        $w_text tag remove misspelled "$lineno.0" "$lineno.end"
diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl
new file mode 100644 (file)
index 0000000..82a1a80
--- /dev/null
@@ -0,0 +1,126 @@
+# git-gui about git-gui dialog
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc find_ssh_key {} {
+       foreach name {~/.ssh/id_dsa.pub ~/.ssh/id_rsa.pub ~/.ssh/identity.pub} {
+               if {[file exists $name]} {
+                       set fh    [open $name r]
+                       set cont  [read $fh]
+                       close $fh
+                       return [list $name $cont]
+               }
+       }
+
+       return {}
+}
+
+proc do_ssh_key {} {
+       global sshkey_title have_tk85 sshkey_fd
+
+       set w .sshkey_dialog
+       if {[winfo exists $w]} {
+               raise $w
+               return
+       }
+
+       toplevel $w
+       wm transient $w .
+
+       set finfo [find_ssh_key]
+       if {$finfo eq {}} {
+               set sshkey_title [mc "No keys found."]
+               set gen_state   normal
+       } else {
+               set sshkey_title [mc "Found a public key in: %s" [lindex $finfo 0]]
+               set gen_state   disabled
+       }
+
+       frame $w.header -relief flat
+       label $w.header.lbl -textvariable sshkey_title -anchor w
+       button $w.header.gen -text [mc "Generate Key"] \
+               -command [list make_ssh_key $w] -state $gen_state
+       pack $w.header.lbl -side left -expand 1 -fill x
+       pack $w.header.gen -side right
+       pack $w.header -fill x -pady 5 -padx 5
+
+       text $w.contents -width 60 -height 10 -wrap char -relief sunken
+       pack $w.contents -fill both -expand 1
+       if {$have_tk85} {
+               $w.contents configure -inactiveselectbackground darkblue
+       }
+
+       frame $w.buttons
+       button $w.buttons.close -text [mc Close] \
+               -default active -command [list destroy $w]
+       pack $w.buttons.close -side right
+       button $w.buttons.copy -text [mc "Copy To Clipboard"] \
+               -command [list tk_textCopy $w.contents]
+       pack $w.buttons.copy -side left
+       pack $w.buttons -side bottom -fill x -pady 5 -padx 5
+
+       if {$finfo ne {}} {
+               $w.contents insert end [lindex $finfo 1] sel
+       }
+       $w.contents configure -state disabled
+
+       bind $w <Visibility> "grab $w; focus $w.buttons.close"
+       bind $w <Key-Escape> "destroy $w"
+       bind $w <Key-Return> "destroy $w"
+       bind $w <Destroy> kill_sshkey
+       wm title $w [mc "Your OpenSSH Public Key"]
+       tk::PlaceWindow $w widget .
+       tkwait window $w
+}
+
+proc make_ssh_key {w} {
+       global sshkey_title sshkey_output sshkey_fd
+
+       set sshkey_title [mc "Generating..."]
+       $w.header.gen configure -state disabled
+
+       set cmdline [list sh -c {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
+
+       if {[catch { set sshkey_fd [_open_stdout_stderr $cmdline] } err]} {
+               error_popup [mc "Could not start ssh-keygen:\n\n%s" $err]
+               return
+       }
+
+       set sshkey_output {}
+       fconfigure $sshkey_fd -blocking 0
+       fileevent $sshkey_fd readable [list read_sshkey_output $sshkey_fd $w]
+}
+
+proc kill_sshkey {} {
+       global sshkey_fd
+       if {![info exists sshkey_fd]} return
+       catch { kill_file_process $sshkey_fd }
+       catch { close $sshkey_fd }
+}
+
+proc read_sshkey_output {fd w} {
+       global sshkey_fd sshkey_output sshkey_title
+
+       set sshkey_output "$sshkey_output[read $fd]"
+       if {![eof $fd]} return
+
+       fconfigure $fd -blocking 1
+       unset sshkey_fd
+
+       $w.contents configure -state normal
+       if {[catch {close $fd} err]} {
+               set sshkey_title [mc "Generation failed."]
+               $w.contents insert end $err
+               $w.contents insert end "\n"
+               $w.contents insert end $sshkey_output
+       } else {
+               set finfo [find_ssh_key]
+               if {$finfo eq {}} {
+                       set sshkey_title [mc "Generation succeded, but no keys found."]
+                       $w.contents insert end $sshkey_output
+               } else {
+                       set sshkey_title [mc "Your key is in: %s" [lindex $finfo 0]]
+                       $w.contents insert end [lindex $finfo 1] sel
+               }
+       }
+       $w.contents configure -state disable
+}
index 8e6a9d0a6010cf5efee069a2d488124bcbdb54cd..e419d7810ae404e41a0a85080d16f4576d737d8a 100644 (file)
@@ -135,7 +135,7 @@ proc do_push_anywhere {} {
                set push_urltype url
        }
        radiobutton $w.dest.url_r \
-               -text [mc "Arbitrary URL:"] \
+               -text [mc "Arbitrary Location:"] \
                -value url \
                -variable push_urltype
        entry $w.dest.url_t \
index 793cca1e798640400453e74e2a4b64863f76a540..5c04812b1ab67c29fb68a4c3d7d4e97bb6483984 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-09-13 10:20+0200\n"
-"PO-Revision-Date: 2008-09-13 10:24+0200\n"
+"POT-Creation-Date: 2008-10-25 13:32+0200\n"
+"PO-Revision-Date: 2008-10-25 22:47+0200\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
@@ -86,7 +86,17 @@ msgstr "Dateistatus aktualisieren..."
 msgid "Scanning for modified files ..."
 msgstr "Nach geänderten Dateien suchen..."
 
-#: git-gui.sh:1324 lib/browser.tcl:246
+#: git-gui.sh:1325
+#, fuzzy
+msgid "Calling prepare-commit-msg hook..."
+msgstr "Aufrufen der Vor-Eintragen-Kontrolle..."
+
+#: git-gui.sh:1342
+#, fuzzy
+msgid "Commit declined by prepare-commit-msg hook."
+msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+
+#: git-gui.sh:1502 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Bereit."
 
@@ -170,7 +180,11 @@ msgstr "Zusammenführen"
 msgid "Remote"
 msgstr "Andere Archive"
 
-#: git-gui.sh:1879
+#: git-gui.sh:2242
+msgid "Explore Working Copy"
+msgstr "Arbeitskopie im Dateimanager"
+
+#: git-gui.sh:2247
 msgid "Browse Current Branch's Files"
 msgstr "Aktuellen Zweig durchblättern"
 
@@ -267,7 +281,15 @@ msgstr "Löschen..."
 msgid "Reset..."
 msgstr "Zurücksetzen..."
 
-#: git-gui.sh:2002 git-gui.sh:2389
+#: git-gui.sh:2372
+msgid "Done"
+msgstr "Fertig"
+
+#: git-gui.sh:2374
+msgid "Commit@@verb"
+msgstr "Eintragen"
+
+#: git-gui.sh:2383 git-gui.sh:2786
 msgid "New Commit"
 msgstr "Neue Version"
 
@@ -307,11 +329,7 @@ msgstr "Mehr Zeilen anzeigen"
 msgid "Sign Off"
 msgstr "Abzeichnen"
 
-#: git-gui.sh:2053 git-gui.sh:2372
-msgid "Commit@@verb"
-msgstr "Eintragen"
-
-#: git-gui.sh:2064
+#: git-gui.sh:2458
 msgid "Local Merge..."
 msgstr "Lokales Zusammenführen..."
 
@@ -319,11 +337,19 @@ msgstr "Lokales Zusammenführen..."
 msgid "Abort Merge..."
 msgstr "Zusammenführen abbrechen..."
 
-#: git-gui.sh:2081
+#: git-gui.sh:2475
+msgid "Add..."
+msgstr "Hinzufügen..."
+
+#: git-gui.sh:2479
 msgid "Push..."
 msgstr "Versenden..."
 
-#: git-gui.sh:2197 git-gui.sh:2219 lib/about.tcl:14
+#: git-gui.sh:2483
+msgid "Delete Branch..."
+msgstr "Zweig löschen..."
+
+#: git-gui.sh:2493 git-gui.sh:2515 lib/about.tcl:14
 #: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
 #, tcl-format
 msgid "About %s"
@@ -416,7 +442,11 @@ msgstr "Schriftgröße verkleinern"
 msgid "Increase Font Size"
 msgstr "Schriftgröße vergrößern"
 
-#: git-gui.sh:2870
+#: git-gui.sh:3033 lib/blame.tcl:281
+msgid "Encoding"
+msgstr "Zeichenkodierung"
+
+#: git-gui.sh:3044
 msgid "Apply/Reverse Hunk"
 msgstr "Kontext anwenden/umkehren"
 
@@ -440,11 +470,7 @@ msgstr "Lokale Version benutzen"
 msgid "Revert To Base"
 msgstr "Ursprüngliche Version benutzen"
 
-#: git-gui.sh:2906
-msgid "Stage Working Copy"
-msgstr "Arbeitskopie bereitstellen"
-
-#: git-gui.sh:2925
+#: git-gui.sh:3091
 msgid "Unstage Hunk From Commit"
 msgstr "Kontext aus Bereitstellung herausnehmen"
 
@@ -583,7 +609,12 @@ msgstr "Eintragender:"
 msgid "Original File:"
 msgstr "Ursprüngliche Datei:"
 
-#: lib/blame.tcl:990
+#: lib/blame.tcl:1013
+#, fuzzy
+msgid "Cannot find HEAD commit:"
+msgstr "Elternversion kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1068
 msgid "Cannot find parent commit:"
 msgstr "Elternversion kann nicht gefunden werden:"
 
@@ -1041,11 +1072,15 @@ msgstr "Datei »%s« existiert bereits."
 msgid "Clone"
 msgstr "Klonen"
 
-#: lib/choose_repository.tcl:468
-msgid "URL:"
-msgstr "URL:"
+#: lib/choose_repository.tcl:467
+msgid "Source Location:"
+msgstr ""
+
+#: lib/choose_repository.tcl:478
+msgid "Target Directory:"
+msgstr "Zielverzeichnis:"
 
-#: lib/choose_repository.tcl:489
+#: lib/choose_repository.tcl:490
 msgid "Clone Type:"
 msgstr "Art des Klonens:"
 
@@ -1525,7 +1560,27 @@ msgstr ""
 msgid "Loading diff of %s..."
 msgstr "Vergleich von »%s« laden..."
 
-#: lib/diff.tcl:114 lib/diff.tcl:184
+#: lib/diff.tcl:120
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
+msgstr ""
+
+#: lib/diff.tcl:125
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
+msgstr ""
+
+#: lib/diff.tcl:132
+msgid "LOCAL:\n"
+msgstr ""
+
+#: lib/diff.tcl:135
+msgid "REMOTE:\n"
+msgstr ""
+
+#: lib/diff.tcl:197 lib/diff.tcl:296
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Datei »%s« kann nicht angezeigt werden"
@@ -1542,7 +1597,22 @@ msgstr "Git-Projektarchiv (Unterprojekt)"
 msgid "* Binary file (not showing content)."
 msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
 
-#: lib/diff.tcl:313
+#: lib/diff.tcl:222
+#, tcl-format
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
+msgstr ""
+
+#: lib/diff.tcl:228
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+
+#: lib/diff.tcl:437
 msgid "Failed to unstage selected hunk."
 msgstr ""
 "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
@@ -1559,6 +1629,19 @@ msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
 msgid "Failed to stage selected line."
 msgstr "Fehler beim Bereitstellen der gewählten Zeile."
 
+#: lib/encoding.tcl:443
+msgid "Default"
+msgstr "Voreinstellung"
+
+#: lib/encoding.tcl:448
+#, tcl-format
+msgid "System (%s)"
+msgstr "Systemweit (%s)"
+
+#: lib/encoding.tcl:459 lib/encoding.tcl:465
+msgid "Other"
+msgstr "Andere"
+
 #: lib/error.tcl:20 lib/error.tcl:114
 msgid "error"
 msgstr "Fehler"
@@ -1811,7 +1894,12 @@ msgstr ""
 "Diese Operation kann nur rückgängig gemacht werden, wenn die\n"
 "Zusammenführung erneut gestartet wird."
 
-#: lib/mergetool.tcl:32
+#: lib/mergetool.tcl:45
+#, tcl-format
+msgid "File %s seems to have unresolved conflicts, still stage?"
+msgstr "Datei »%s« hat nicht aufgelöste Konflikte. Trotzdem bereitstellen?"
+
+#: lib/mergetool.tcl:60
 #, tcl-format
 msgid "Adding resolution for %s"
 msgstr "Auflösung hinzugefügt für %s"
@@ -1868,12 +1956,17 @@ msgstr "Zusammenführungswerkzeug starten..."
 msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
-#: lib/mergetool.tcl:353
+#: lib/option.tcl:11
+#, tcl-format
+msgid "Invalid global encoding '%s'"
+msgstr "Ungültige globale Zeichenkodierung »%s«"
+
+#: lib/option.tcl:19
 #, tcl-format
-msgid "File %s unchanged, still accept as resolved?"
-msgstr "Datei »%s« unverändert. Trotzdem Konflikt als gelöst akzeptieren?"
+msgid "Invalid repo encoding '%s'"
+msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
 
-#: lib/option.tcl:95
+#: lib/option.tcl:117
 msgid "Restore Defaults"
 msgstr "Voreinstellungen wiederherstellen"
 
@@ -1950,7 +2043,15 @@ msgstr "Textbreite der Versionsbeschreibung"
 msgid "New Branch Name Template"
 msgstr "Namensvorschlag für neue Zweige"
 
-#: lib/option.tcl:192
+#: lib/option.tcl:155
+msgid "Default File Contents Encoding"
+msgstr "Vorgestellte Zeichenkodierung"
+
+#: lib/option.tcl:203
+msgid "Change"
+msgstr "Ändern"
+
+#: lib/option.tcl:230
 msgid "Spelling Dictionary:"
 msgstr "Wörterbuch Rechtschreibprüfung:"
 
@@ -1975,9 +2076,85 @@ msgstr "Einstellungen"
 msgid "Failed to completely save options:"
 msgstr "Optionen konnten nicht gespeichert werden:"
 
+#: lib/remote_add.tcl:19
+msgid "Add Remote"
+msgstr "Anderes Archiv hinzufügen"
+
+#: lib/remote_add.tcl:24
+msgid "Add New Remote"
+msgstr "Neues anderes Archiv hinzufügen"
+
+#: lib/remote_add.tcl:28
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: lib/remote_add.tcl:37
+msgid "Remote Details"
+msgstr "Einzelheiten des anderen Archivs"
+
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Adresse:"
+
+#: lib/remote_add.tcl:62
+msgid "Further Action"
+msgstr "Weitere Aktion jetzt"
+
+#: lib/remote_add.tcl:65
+msgid "Fetch Immediately"
+msgstr "Gleich anfordern"
+
+#: lib/remote_add.tcl:71
+msgid "Initialize Remote Repository and Push"
+msgstr "Anderes Archiv initialisieren und dahin versenden"
+
+#: lib/remote_add.tcl:77
+msgid "Do Nothing Else Now"
+msgstr "Nichts tun"
+
+#: lib/remote_add.tcl:101
+#, fuzzy
+msgid "Please supply a remote name."
+msgstr "Bitte geben Sie einen Zweignamen an."
+
+#: lib/remote_add.tcl:114
+#, fuzzy, tcl-format
+msgid "'%s' is not an acceptable remote name."
+msgstr "»%s« ist kein zulässiger Zweigname."
+
+#: lib/remote_add.tcl:125
+#, fuzzy, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr "Fehler beim Umbenennen von »%s«."
+
+#: lib/remote_add.tcl:133 lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "»%s« anfordern"
+
+#: lib/remote_add.tcl:134
+#, fuzzy, tcl-format
+msgid "Fetching the %s"
+msgstr "Änderungen »%s« von »%s« anfordern"
+
+#: lib/remote_add.tcl:157
+#, tcl-format
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr "Initialisieren eines anderen Archivs an Adresse »%s« ist nicht möglich."
+
+#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "»%s« versenden..."
+
+#: lib/remote_add.tcl:164
+#, tcl-format
+msgid "Setting up the %s (at %s)"
+msgstr "Einrichten von »%s« an »%s«"
+
 #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
-msgid "Delete Remote Branch"
-msgstr "Zweig in anderem Projektarchiv löschen"
+msgid "Delete Branch Remotely"
+msgstr "Zweig in anderem Archiv löschen"
 
 #: lib/remote_branch_delete.tcl:47
 msgid "From Repository"
@@ -1988,8 +2165,8 @@ msgid "Remote:"
 msgstr "Anderes Archiv:"
 
 #: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
-msgid "Arbitrary URL:"
-msgstr "Archiv-URL:"
+msgid "Arbitrary Location:"
+msgstr "Adresse:"
 
 #: lib/remote_branch_delete.tcl:84
 msgid "Branches"
@@ -2061,7 +2238,11 @@ msgstr "Kein Projektarchiv ausgewählt."
 msgid "Scanning %s..."
 msgstr "»%s« laden..."
 
-#: lib/remote.tcl:165
+#: lib/remote.tcl:163
+msgid "Remove Remote"
+msgstr "Anderes Archiv entfernen"
+
+#: lib/remote.tcl:168
 msgid "Prune from"
 msgstr "Aufräumen von"
 
@@ -2073,6 +2254,22 @@ msgstr "Anfordern von"
 msgid "Push to"
 msgstr "Versenden nach"
 
+#: lib/search.tcl:21
+msgid "Find:"
+msgstr "Suchen:"
+
+#: lib/search.tcl:22
+msgid "Next"
+msgstr "Nächster"
+
+#: lib/search.tcl:23
+msgid "Prev"
+msgstr "Voriger"
+
+#: lib/search.tcl:24
+msgid "Case-Sensitive"
+msgstr ""
+
 #: lib/shortcut.tcl:20 lib/shortcut.tcl:61
 msgid "Cannot write shortcut:"
 msgstr "Fehler beim Schreiben der Verknüpfung:"
@@ -2123,11 +2320,6 @@ msgstr "Rechtschreibprüfung fehlgeschlagen"
 msgid "%s ... %*i of %*i %s (%3i%%)"
 msgstr "%s ... %*i von %*i %s (%3i%%)"
 
-#: lib/transport.tcl:6
-#, tcl-format
-msgid "fetch %s"
-msgstr "»%s« anfordern"
-
 #: lib/transport.tcl:7
 #, tcl-format
 msgid "Fetching new changes from %s"
@@ -2143,11 +2335,6 @@ msgstr "Aufräumen von »%s«"
 msgid "Pruning tracking branches deleted from %s"
 msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
 
-#: lib/transport.tcl:25 lib/transport.tcl:71
-#, tcl-format
-msgid "push %s"
-msgstr "»%s« versenden..."
-
 #: lib/transport.tcl:26
 #, tcl-format
 msgid "Pushing changes to %s"
index 39f8d73dfa4377922fd8ebc406643d73338916d0..1172e47571dfe1d6dd088381d63045fd5eae3db3 100755 (executable)
@@ -173,13 +173,18 @@ pick_one_preserving_merges () {
 
        if test -f "$DOTEST"/current-commit
        then
-               current_commit=$(cat "$DOTEST"/current-commit) &&
-               git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
-               rm "$DOTEST"/current-commit ||
-               die "Cannot write current commit's replacement sha1"
+               if test "$fast_forward" = t
+               then
+                       cat "$DOTEST"/current-commit | while read current_commit
+                       do
+                               git rev-parse HEAD > "$REWRITTEN"/$current_commit
+                       done
+                       rm "$DOTEST"/current-commit ||
+                       die "Cannot write current commit's replacement sha1"
+               fi
        fi
 
-       echo $sha1 > "$DOTEST"/current-commit
+       echo $sha1 >> "$DOTEST"/current-commit
 
        # rewrite parents; if none were rewritten, we can fast-forward.
        new_parents=
@@ -192,6 +197,15 @@ pick_one_preserving_merges () {
                if test -f "$REWRITTEN"/$p
                then
                        new_p=$(cat "$REWRITTEN"/$p)
+
+                       # If the todo reordered commits, and our parent is marked for
+                       # rewriting, but hasn't been gotten to yet, assume the user meant to
+                       # drop it on top of the current HEAD
+                       if test -z "$new_p"
+                       then
+                               new_p=$(git rev-parse HEAD)
+                       fi
+
                        test $p != $new_p && fast_forward=f
                        case "$new_parents" in
                        *$new_p*)
@@ -217,15 +231,19 @@ pick_one_preserving_merges () {
                        die "Cannot fast forward to $sha1"
                ;;
        f)
-               test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
-
                first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
-               # detach HEAD to current parent
-               output git checkout $first_parent 2> /dev/null ||
-                       die "Cannot move HEAD to $first_parent"
+
+               if [ "$1" != "-n" ]
+               then
+                       # detach HEAD to current parent
+                       output git checkout $first_parent 2> /dev/null ||
+                               die "Cannot move HEAD to $first_parent"
+               fi
 
                case "$new_parents" in
                ' '*' '*)
+                       test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+
                        # redo merge
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
@@ -379,20 +397,7 @@ do_next () {
        HEADNAME=$(cat "$DOTEST"/head-name) &&
        OLDHEAD=$(cat "$DOTEST"/head) &&
        SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
-       if test -d "$REWRITTEN"
-       then
-               test -f "$DOTEST"/current-commit &&
-                       current_commit=$(cat "$DOTEST"/current-commit) &&
-                       git rev-parse HEAD > "$REWRITTEN"/$current_commit
-               if test -f "$REWRITTEN"/$OLDHEAD
-               then
-                       NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
-               else
-                       NEWHEAD=$OLDHEAD
-               fi
-       else
-               NEWHEAD=$(git rev-parse HEAD)
-       fi &&
+       NEWHEAD=$(git rev-parse HEAD) &&
        case $HEADNAME in
        refs/*)
                message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
@@ -591,18 +596,69 @@ first and then run 'git rebase --continue' again."
                                echo $ONTO > "$REWRITTEN"/$c ||
                                        die "Could not init rewritten commits"
                        done
+                       # No cherry-pick because our first pass is to determine
+                       # parents to rewrite and skipping dropped commits would
+                       # prematurely end our probe
                        MERGES_OPTION=
+                       first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)"
                else
-                       MERGES_OPTION=--no-merges
+                       MERGES_OPTION="--no-merges --cherry-pick"
                fi
 
                SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
                SHORTHEAD=$(git rev-parse --short $HEAD)
                SHORTONTO=$(git rev-parse --short $ONTO)
                git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
-                       --abbrev=7 --reverse --left-right --cherry-pick \
+                       --abbrev=7 --reverse --left-right --topo-order \
                        $UPSTREAM...$HEAD | \
-                       sed -n "s/^>/pick /p" > "$TODO"
+                       sed -n "s/^>//p" | while read shortsha1 rest
+               do
+                       if test t != "$PRESERVE_MERGES"
+                       then
+                               echo "pick $shortsha1 $rest" >> "$TODO"
+                       else
+                               sha1=$(git rev-parse $shortsha1)
+                               preserve=t
+                               for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
+                               do
+                                       if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+                                       then
+                                               preserve=f
+                                       fi
+                               done
+                               if test f = "$preserve"
+                               then
+                                       touch "$REWRITTEN"/$sha1
+                                       echo "pick $shortsha1 $rest" >> "$TODO"
+                               fi
+                       fi
+               done
+
+               # Watch for commits that been dropped by --cherry-pick
+               if test t = "$PRESERVE_MERGES"
+               then
+                       mkdir "$DROPPED"
+                       # Save all non-cherry-picked changes
+                       git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
+                               sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
+                       # Now all commits and note which ones are missing in
+                       # not-cherry-picks and hence being dropped
+                       git rev-list $UPSTREAM..$HEAD |
+                       while read rev
+                       do
+                               if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
+                               then
+                                       # Use -f2 because if rev-list is telling us this commit is
+                                       # not worthwhile, we don't want to track its multiple heads,
+                                       # just the history of its first-parent for others that will
+                                       # be rebasing on top of it
+                                       git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
+                                       short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
+                                       grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
+                                       rm "$REWRITTEN"/$rev
+                               fi
+                       done
+               fi
                test -s "$TODO" || echo noop >> "$TODO"
                cat >> "$TODO" << EOF
 
@@ -618,28 +674,6 @@ first and then run 'git rebase --continue' again."
 #
 EOF
 
-               # Watch for commits that been dropped by --cherry-pick
-               if test t = "$PRESERVE_MERGES"
-               then
-                       mkdir "$DROPPED"
-                       # drop the --cherry-pick parameter this time
-                       git rev-list $MERGES_OPTION --abbrev-commit \
-                               --abbrev=7 $UPSTREAM...$HEAD --left-right | \
-                               sed -n "s/^>//p" | while read rev
-                       do
-                               grep --quiet "$rev" "$TODO"
-                               if [ $? -ne 0 ]
-                               then
-                                       # Use -f2 because if rev-list is telling this commit is not
-                                       # worthwhile, we don't want to track its multiple heads,
-                                       # just the history of its first-parent for others that will
-                                       # be rebasing on top of us
-                                       full=$(git rev-parse $rev)
-                                       git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$full
-                               fi
-                       done
-               fi
-
                has_action "$TODO" ||
                        die_abort "Nothing to do"
 
index bdbfac66256ffc29e26f5c83531c77a18a263808..94ca5c89ad086bac754fb6b6b907fd4e968e8024 100755 (executable)
@@ -374,10 +374,9 @@ sub read_config {
 
                push @files, grep { -f $_ } map { +$f . "/" . $_ }
                                sort readdir(DH);
-
+               closedir(DH);
        } elsif (-f $f or -p $f) {
                push @files, $f;
-
        } else {
                print STDERR "Skipping $f - not found.\n";
        }
index f90ddac908f213fe8d7d58eea1aba45a04ce781e..5702b100f17d32c4d370d2b29374390ad0f03520 100755 (executable)
@@ -2267,7 +2267,7 @@ sub do_git_commit {
        }
        die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o;
 
-       my @exec = ('git-commit-tree', $tree);
+       my @exec = ('git', 'commit-tree', $tree);
        foreach ($self->get_commit_parents($log_entry)) {
                push @exec, '-p', $_;
        }
index c5254afa7fabbc38c2abf188570cd5ab463b4c37..9d1af7e55703c87168e949c285a97fbce867d3e5 100755 (executable)
@@ -534,23 +534,87 @@ sub evaluate_path_info {
        return if $input_params{'action'};
        $path_info =~ s,^\Q$project\E/*,,;
 
-       my ($refname, $pathname) = split(/:/, $path_info, 2);
+       # next, check if we have an action
+       my $action = $path_info;
+       $action =~ s,/.*$,,;
+       if (exists $actions{$action}) {
+               $path_info =~ s,^$action/*,,;
+               $input_params{'action'} = $action;
+       }
+
+       # list of actions that want hash_base instead of hash, but can have no
+       # pathname (f) parameter
+       my @wants_base = (
+               'tree',
+               'history',
+       );
+
+       # we want to catch
+       # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
+       my ($parentrefname, $parentpathname, $refname, $pathname) =
+               ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
+
+       # first, analyze the 'current' part
        if (defined $pathname) {
-               # we got "project.git/branch:filename" or "project.git/branch:dir/"
-               # we could use git_get_type(branch:pathname), but it needs $git_dir
+               # we got "branch:filename" or "branch:dir/"
+               # we could use git_get_type(branch:pathname), but:
+               # - it needs $git_dir
+               # - it does a git() call
+               # - the convention of terminating directories with a slash
+               #   makes it superfluous
+               # - embedding the action in the PATH_INFO would make it even
+               #   more superfluous
                $pathname =~ s,^/+,,;
                if (!$pathname || substr($pathname, -1) eq "/") {
-                       $input_params{'action'} = "tree";
+                       $input_params{'action'} ||= "tree";
                        $pathname =~ s,/$,,;
                } else {
-                       $input_params{'action'} = "blob_plain";
+                       # the default action depends on whether we had parent info
+                       # or not
+                       if ($parentrefname) {
+                               $input_params{'action'} ||= "blobdiff_plain";
+                       } else {
+                               $input_params{'action'} ||= "blob_plain";
+                       }
                }
                $input_params{'hash_base'} ||= $refname;
                $input_params{'file_name'} ||= $pathname;
        } elsif (defined $refname) {
-               # we got "project.git/branch"
-               $input_params{'action'} = "shortlog";
-               $input_params{'hash'} ||= $refname;
+               # we got "branch". In this case we have to choose if we have to
+               # set hash or hash_base.
+               #
+               # Most of the actions without a pathname only want hash to be
+               # set, except for the ones specified in @wants_base that want
+               # hash_base instead. It should also be noted that hand-crafted
+               # links having 'history' as an action and no pathname or hash
+               # set will fail, but that happens regardless of PATH_INFO.
+               $input_params{'action'} ||= "shortlog";
+               if (grep { $_ eq $input_params{'action'} } @wants_base) {
+                       $input_params{'hash_base'} ||= $refname;
+               } else {
+                       $input_params{'hash'} ||= $refname;
+               }
+       }
+
+       # next, handle the 'parent' part, if present
+       if (defined $parentrefname) {
+               # a missing pathspec defaults to the 'current' filename, allowing e.g.
+               # someproject/blobdiff/oldrev..newrev:/filename
+               if ($parentpathname) {
+                       $parentpathname =~ s,^/+,,;
+                       $parentpathname =~ s,/$,,;
+                       $input_params{'file_parent'} ||= $parentpathname;
+               } else {
+                       $input_params{'file_parent'} ||= $input_params{'file_name'};
+               }
+               # we assume that hash_parent_base is wanted if a path was specified,
+               # or if the action wants hash_base instead of hash
+               if (defined $input_params{'file_parent'} ||
+                       grep { $_ eq $input_params{'action'} } @wants_base) {
+                       $input_params{'hash_parent_base'} ||= $parentrefname;
+               } else {
+                       $input_params{'hash_parent'} ||= $parentrefname;
+               }
        }
 }
 evaluate_path_info();
@@ -699,14 +763,61 @@ (%)
 
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
        if ($use_pathinfo) {
-               # use PATH_INFO for project name
+               # try to put as many parameters as possible in PATH_INFO:
+               #   - project name
+               #   - action
+               #   - hash_parent or hash_parent_base:/file_parent
+               #   - hash or hash_base:/filename
+
+               # When the script is the root DirectoryIndex for the domain,
+               # $href here would be something like http://gitweb.example.com/
+               # Thus, we strip any trailing / from $href, to spare us double
+               # slashes in the final URL
+               $href =~ s,/$,,;
+
+               # Then add the project name, if present
                $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
                delete $params{'project'};
 
-               # Summary just uses the project path URL
-               if (defined $params{'action'} && $params{'action'} eq 'summary') {
+               # Summary just uses the project path URL, any other action is
+               # added to the URL
+               if (defined $params{'action'}) {
+                       $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
                        delete $params{'action'};
                }
+
+               # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
+               # stripping nonexistent or useless pieces
+               $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
+                       || $params{'hash_parent'} || $params{'hash'});
+               if (defined $params{'hash_base'}) {
+                       if (defined $params{'hash_parent_base'}) {
+                               $href .= esc_url($params{'hash_parent_base'});
+                               # skip the file_parent if it's the same as the file_name
+                               delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
+                               if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
+                                       $href .= ":/".esc_url($params{'file_parent'});
+                                       delete $params{'file_parent'};
+                               }
+                               $href .= "..";
+                               delete $params{'hash_parent'};
+                               delete $params{'hash_parent_base'};
+                       } elsif (defined $params{'hash_parent'}) {
+                               $href .= esc_url($params{'hash_parent'}). "..";
+                               delete $params{'hash_parent'};
+                       }
+
+                       $href .= esc_url($params{'hash_base'});
+                       if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
+                               $href .= ":/".esc_url($params{'file_name'});
+                               delete $params{'file_name'};
+                       }
+                       delete $params{'hash'};
+                       delete $params{'hash_base'};
+               } elsif (defined $params{'hash'}) {
+                       $href .= esc_url($params{'hash'});
+                       delete $params{'hash'};
+               }
        }
 
        # now encode the parameters explicitly
@@ -1846,7 +1957,10 @@ sub git_get_project_ctags {
        my $ctags = {};
 
        $git_dir = "$projectroot/$path";
-       foreach (<$git_dir/ctags/*>) {
+       unless (opendir D, "$git_dir/ctags") {
+               return $ctags;
+       }
+       foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir(D)) {
                open CT, $_ or next;
                my $val = <CT>;
                chomp $val;
@@ -1854,6 +1968,7 @@ sub git_get_project_ctags {
                my $ctag = $_; $ctag =~ s#.*/##;
                $ctags->{$ctag} = $val;
        }
+       closedir D;
        $ctags;
 }
 
index 6f89bb9ac7f281434a9759956d8da23e6bc071db..fe75332a9cd492fa109d07904de39f0eafa75c7e 100644 (file)
@@ -221,17 +221,23 @@ static void bad_object(unsigned long offset, const char *format, ...)
        die("pack has bad object at offset %lu: %s", offset, buf);
 }
 
+static void free_base_data(struct base_data *c)
+{
+       if (c->data) {
+               free(c->data);
+               c->data = NULL;
+               base_cache_used -= c->size;
+       }
+}
+
 static void prune_base_data(struct base_data *retain)
 {
        struct base_data *b = base_cache;
        for (b = base_cache;
             base_cache_used > delta_base_cache_limit && b;
             b = b->child) {
-               if (b->data && b != retain) {
-                       free(b->data);
-                       b->data = NULL;
-                       base_cache_used -= b->size;
-               }
+               if (b->data && b != retain)
+                       free_base_data(b);
        }
 }
 
@@ -244,7 +250,8 @@ static void link_base_data(struct base_data *base, struct base_data *c)
 
        c->base = base;
        c->child = NULL;
-       base_cache_used += c->size;
+       if (c->data)
+               base_cache_used += c->size;
        prune_base_data(c);
 }
 
@@ -255,10 +262,7 @@ static void unlink_base_data(struct base_data *c)
                base->child = NULL;
        else
                base_cache = NULL;
-       if (c->data) {
-               free(c->data);
-               base_cache_used -= c->size;
-       }
+       free_base_data(c);
 }
 
 static void *unpack_entry_data(unsigned long offset, unsigned long size)
@@ -408,22 +412,24 @@ static int find_delta(const union delta_base *base)
         return -first-1;
 }
 
-static int find_delta_children(const union delta_base *base,
-                              int *first_index, int *last_index)
+static void find_delta_children(const union delta_base *base,
+                               int *first_index, int *last_index)
 {
        int first = find_delta(base);
        int last = first;
        int end = nr_deltas - 1;
 
-       if (first < 0)
-               return -1;
+       if (first < 0) {
+               *first_index = 0;
+               *last_index = -1;
+               return;
+       }
        while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
                --first;
        while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
                ++last;
        *first_index = first;
        *last_index = last;
-       return 0;
 }
 
 static void sha1_object(const void *data, unsigned long size,
@@ -494,8 +500,10 @@ static void *get_base_data(struct base_data *c)
                        free(raw);
                        if (!c->data)
                                bad_object(obj->idx.offset, "failed to apply delta");
-               } else
+               } else {
                        c->data = get_data_from_pack(obj);
+                       c->size = obj->size;
+               }
 
                base_cache_used += c->size;
                prune_base_data(c);
@@ -504,49 +512,74 @@ static void *get_base_data(struct base_data *c)
 }
 
 static void resolve_delta(struct object_entry *delta_obj,
-                         struct base_data *base_obj, enum object_type type)
+                         struct base_data *base, struct base_data *result)
 {
-       void *delta_data;
-       unsigned long delta_size;
-       union delta_base delta_base;
-       int j, first, last;
-       struct base_data result;
+       void *base_data, *delta_data;
 
-       delta_obj->real_type = type;
+       delta_obj->real_type = base->obj->real_type;
        delta_data = get_data_from_pack(delta_obj);
-       delta_size = delta_obj->size;
-       result.data = patch_delta(get_base_data(base_obj), base_obj->size,
-                            delta_data, delta_size,
-                            &result.size);
+       base_data = get_base_data(base);
+       result->obj = delta_obj;
+       result->data = patch_delta(base_data, base->size,
+                                  delta_data, delta_obj->size, &result->size);
        free(delta_data);
-       if (!result.data)
+       if (!result->data)
                bad_object(delta_obj->idx.offset, "failed to apply delta");
-       sha1_object(result.data, result.size, type, delta_obj->idx.sha1);
+       sha1_object(result->data, result->size, delta_obj->real_type,
+                   delta_obj->idx.sha1);
        nr_resolved_deltas++;
+}
+
+static void find_unresolved_deltas(struct base_data *base,
+                                  struct base_data *prev_base)
+{
+       int i, ref_first, ref_last, ofs_first, ofs_last;
+
+       /*
+        * This is a recursive function. Those brackets should help reducing
+        * stack usage by limiting the scope of the delta_base union.
+        */
+       {
+               union delta_base base_spec;
+
+               hashcpy(base_spec.sha1, base->obj->idx.sha1);
+               find_delta_children(&base_spec, &ref_first, &ref_last);
+
+               memset(&base_spec, 0, sizeof(base_spec));
+               base_spec.offset = base->obj->idx.offset;
+               find_delta_children(&base_spec, &ofs_first, &ofs_last);
+       }
 
-       result.obj = delta_obj;
-       link_base_data(base_obj, &result);
+       if (ref_last == -1 && ofs_last == -1) {
+               free(base->data);
+               return;
+       }
+
+       link_base_data(prev_base, base);
 
-       hashcpy(delta_base.sha1, delta_obj->idx.sha1);
-       if (!find_delta_children(&delta_base, &first, &last)) {
-               for (j = first; j <= last; j++) {
-                       struct object_entry *child = objects + deltas[j].obj_no;
-                       if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, &result, type);
+       for (i = ref_first; i <= ref_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_REF_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ref_last && ofs_last == -1)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
                }
        }
 
-       memset(&delta_base, 0, sizeof(delta_base));
-       delta_base.offset = delta_obj->idx.offset;
-       if (!find_delta_children(&delta_base, &first, &last)) {
-               for (j = first; j <= last; j++) {
-                       struct object_entry *child = objects + deltas[j].obj_no;
-                       if (child->real_type == OBJ_OFS_DELTA)
-                               resolve_delta(child, &result, type);
+       for (i = ofs_first; i <= ofs_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_OFS_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ofs_last)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
                }
        }
 
-       unlink_base_data(&result);
+       unlink_base_data(base);
 }
 
 static int compare_delta_entry(const void *a, const void *b)
@@ -622,37 +655,13 @@ static void parse_pack_objects(unsigned char *sha1)
                progress = start_progress("Resolving deltas", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               union delta_base base;
-               int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
                struct base_data base_obj;
 
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
-               hashcpy(base.sha1, obj->idx.sha1);
-               ref = !find_delta_children(&base, &ref_first, &ref_last);
-               memset(&base, 0, sizeof(base));
-               base.offset = obj->idx.offset;
-               ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
-               if (!ref && !ofs)
-                       continue;
-               base_obj.data = get_data_from_pack(obj);
-               base_obj.size = obj->size;
                base_obj.obj = obj;
-               link_base_data(NULL, &base_obj);
-
-               if (ref)
-                       for (j = ref_first; j <= ref_last; j++) {
-                               struct object_entry *child = objects + deltas[j].obj_no;
-                               if (child->real_type == OBJ_REF_DELTA)
-                                       resolve_delta(child, &base_obj, obj->type);
-                       }
-               if (ofs)
-                       for (j = ofs_first; j <= ofs_last; j++) {
-                               struct object_entry *child = objects + deltas[j].obj_no;
-                               if (child->real_type == OBJ_OFS_DELTA)
-                                       resolve_delta(child, &base_obj, obj->type);
-                       }
-               unlink_base_data(&base_obj);
+               base_obj.data = NULL;
+               find_unresolved_deltas(&base_obj, NULL);
                display_progress(progress, nr_resolved_deltas);
        }
 }
@@ -745,7 +754,6 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
        for (i = 0; i < n; i++) {
                struct delta_entry *d = sorted_by_pos[i];
                enum object_type type;
-               int j, first, last;
                struct base_data base_obj;
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
@@ -759,16 +767,7 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
                base_obj.obj = append_obj_to_pack(f, d->base.sha1,
                                        base_obj.data, base_obj.size, type);
-               link_base_data(NULL, &base_obj);
-
-               find_delta_children(&d->base, &first, &last);
-               for (j = first; j <= last; j++) {
-                       struct object_entry *child = objects + deltas[j].obj_no;
-                       if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, &base_obj, type);
-               }
-
-               unlink_base_data(&base_obj);
+               find_unresolved_deltas(&base_obj, NULL);
                display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
diff --git a/path.c b/path.c
index 092ce57190cc75882a3766c9aee1df719da8a3d6..eb24017535f944a2c5dbc46663c02937cff69cd3 100644 (file)
--- a/path.c
+++ b/path.c
@@ -402,7 +402,7 @@ int normalize_absolute_path(char *buf, const char *path)
                        goto next;
                }
 
-               memcpy(dst, comp_start, comp_len);
+               memmove(dst, comp_start, comp_len);
                dst += comp_len;
        next:
                comp_start = comp_end;
index 6aab712e6ac6513b0b46958d93d536ef975276b4..ba94453781c98a97a95c65999873fc28b013340d 100644 (file)
@@ -1203,8 +1203,7 @@ =head1 COPYRIGHT
 # the method was called upon an instance and (undef, @args) if
 # it was called directly.
 sub _maybe_self {
-       # This breaks inheritance. Oh well.
-       ref $_[0] eq 'Git' ? @_ : (undef, @_);
+       UNIVERSAL::isa($_[0], 'Git') ? @_ : (undef, @_);
 }
 
 # Check if the command id is something reasonable.
index 1e799433395afa935b5ac1454c299ae873873893..f6ff31264b6908bac8bf71678e2eaf2e0cefc100 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -783,6 +783,20 @@ void pp_remainder(enum cmit_fmt fmt,
        }
 }
 
+char *reencode_commit_message(const struct commit *commit, const char **encoding_p)
+{
+       const char *encoding;
+
+       encoding = (git_log_output_encoding
+                   ? git_log_output_encoding
+                   : git_commit_encoding);
+       if (!encoding)
+               encoding = "utf-8";
+       if (encoding_p)
+               *encoding_p = encoding;
+       return logmsg_reencode(commit, encoding);
+}
+
 void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                         struct strbuf *sb, int abbrev,
                         const char *subject, const char *after_subject,
@@ -799,12 +813,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                return;
        }
 
-       encoding = (git_log_output_encoding
-                   ? git_log_output_encoding
-                   : git_commit_encoding);
-       if (!encoding)
-               encoding = "utf-8";
-       reencoded = logmsg_reencode(commit, encoding);
+       reencoded = reencode_commit_message(commit, &encoding);
        if (reencoded) {
                msg = reencoded;
        }
diff --git a/refs.c b/refs.c
index 01e4df4ccfa203f140998f5da42b2a9fcfcca4c7..9ae447b10b79a4ba3c834dd74977db5bd321f055 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -922,25 +922,33 @@ static int repack_without_ref(const char *refname)
        return commit_lock_file(&packlock);
 }
 
-int delete_ref(const char *refname, const unsigned char *sha1)
+int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
 {
        struct ref_lock *lock;
-       int err, i, ret = 0, flag = 0;
+       int err, i = 0, ret = 0, flag = 0;
 
        lock = lock_ref_sha1_basic(refname, sha1, 0, &flag);
        if (!lock)
                return 1;
-       if (!(flag & REF_ISPACKED)) {
+       if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
                /* loose */
-               i = strlen(lock->lk->filename) - 5; /* .lock */
-               lock->lk->filename[i] = 0;
-               err = unlink(lock->lk->filename);
+               const char *path;
+
+               if (!(delopt & REF_NODEREF)) {
+                       i = strlen(lock->lk->filename) - 5; /* .lock */
+                       lock->lk->filename[i] = 0;
+                       path = lock->lk->filename;
+               } else {
+                       path = git_path(refname);
+               }
+               err = unlink(path);
                if (err && errno != ENOENT) {
                        ret = 1;
                        error("unlink(%s) failed: %s",
-                             lock->lk->filename, strerror(errno));
+                             path, strerror(errno));
                }
-               lock->lk->filename[i] = '.';
+               if (!(delopt & REF_NODEREF))
+                       lock->lk->filename[i] = '.';
        }
        /* removing the loose one could have resurrected an earlier
         * packed one.  Also, if it was not loose we need to repack
@@ -965,11 +973,16 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        struct ref_lock *lock;
        struct stat loginfo;
        int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+       const char *symref = NULL;
 
-       if (S_ISLNK(loginfo.st_mode))
+       if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldref);
 
-       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+       symref = resolve_ref(oldref, orig_sha1, 1, &flag);
+       if (flag & REF_ISSYMREF)
+               return error("refname %s is a symbolic ref, renaming it is not supported",
+                       oldref);
+       if (!symref)
                return error("refname %s not found", oldref);
 
        if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
@@ -989,12 +1002,12 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
                return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
                        oldref, strerror(errno));
 
-       if (delete_ref(oldref, orig_sha1)) {
+       if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
                error("unable to delete old %s", oldref);
                goto rollback;
        }
 
-       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
                        if (remove_empty_directories(git_path("%s", newref))) {
                                error("Directory not empty: %s", newref);
@@ -1037,7 +1050,6 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
                error("unable to lock %s for update", newref);
                goto rollback;
        }
-
        lock->force_write = 1;
        hashcpy(lock->old_sha1, orig_sha1);
        if (write_ref_sha1(lock, orig_sha1, logmsg)) {
index 04c2b164bcba0bdead45a50bbb14ceff19e8411f..bd589268fcf459f07ff6c53e43d41e66fe1e7903 100755 (executable)
@@ -75,6 +75,24 @@ test_expect_success "delete $m (by HEAD)" '
 '
 rm -f .git/$m
 
+cp -f .git/HEAD .git/HEAD.orig
+test_expect_success "delete symref without dereference" '
+       git update-ref --no-deref -d HEAD &&
+       ! test -f .git/HEAD
+'
+cp -f .git/HEAD.orig .git/HEAD
+
+test_expect_success "delete symref without dereference when the referred ref is packed" '
+       echo foo >foo.c &&
+       git add foo.c &&
+       git commit -m foo &&
+       git pack-refs --all &&
+       git update-ref --no-deref -d HEAD &&
+       ! test -f .git/HEAD
+'
+cp -f .git/HEAD.orig .git/HEAD
+git update-ref -d $m
+
 test_expect_success '(not) create HEAD with old sha1' "
        test_must_fail git update-ref HEAD $A $B
 "
index 2147eacc5057128facc08816a2980646bed28ec5..25e9971fd86af10c1031f4e8061c31a0c80725a7 100755 (executable)
@@ -112,6 +112,15 @@ test_expect_success 'config information was renamed, too' \
        "test $(git config branch.s.dummy) = Hello &&
         test_must_fail git config branch.s/s/dummy"
 
+test_expect_success 'renaming a symref is not allowed' \
+'
+       git symbolic-ref refs/heads/master2 refs/heads/master &&
+       test_must_fail git branch -m master2 master3 &&
+       git symbolic-ref refs/heads/master2 &&
+       test -f .git/refs/heads/master &&
+       ! test -f .git/refs/heads/master3
+'
+
 test_expect_success \
     'git branch -m u v should fail when the reflog for u is a symlink' '
      git branch -l u &&
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
new file mode 100644 (file)
index 0000000..aacfaae
--- /dev/null
@@ -0,0 +1,135 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Stephen Haberman
+#
+
+test_description='git rebase preserve merges
+
+This test runs git rebase with and tries to squash a commit from after a merge
+to before the merge.
+'
+. ./test-lib.sh
+
+# Copy/paste from t3404-rebase-interactive.sh
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >> fake-editor.sh <<\EOF
+case "$1" in
+*/COMMIT_EDITMSG)
+       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
+       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
+       exit
+       ;;
+esac
+test -z "$EXPECT_COUNT" ||
+       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
+       exit
+test -z "$FAKE_LINES" && exit
+grep -v '^#' < "$1" > "$1".tmp
+rm -f "$1"
+cat "$1".tmp
+action=pick
+for line in $FAKE_LINES; do
+       case $line in
+       squash|edit)
+               action="$line";;
+       *)
+               echo sed -n "${line}s/^pick/$action/p"
+               sed -n "${line}p" < "$1".tmp
+               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
+               action=pick;;
+       esac
+done
+EOF
+
+test_set_editor "$(pwd)/fake-editor.sh"
+chmod a+x fake-editor.sh
+
+# set up two branches like this:
+#
+# A1 - B1 - D1 - E1 - F1
+#       \        /
+#        -- C1 --
+
+test_expect_success 'setup' '
+       touch a &&
+       touch b &&
+       git add a &&
+       git commit -m A1 &&
+       git tag A1
+       git add b &&
+       git commit -m B1 &&
+       git tag B1 &&
+       git checkout -b branch &&
+       touch c &&
+       git add c &&
+       git commit -m C1 &&
+       git checkout master &&
+       touch d &&
+       git add d &&
+       git commit -m D1 &&
+       git merge branch &&
+       touch f &&
+       git add f &&
+       git commit -m F1 &&
+       git tag F1
+'
+
+# Should result in:
+#
+# A1 - B1 - D2 - E2
+#       \        /
+#        -- C1 --
+#
+test_expect_success 'squash F1 into D1' '
+       FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
+       test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" &&
+       test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
+       git tag E2
+'
+
+# Start with:
+#
+# A1 - B1 - D2 - E2
+#  \
+#   G1 ---- L1 ---- M1
+#    \             /
+#     H1 -- J1 -- K1
+#      \         /
+#        -- I1 --
+#
+# And rebase G1..M1 onto E2
+
+test_expect_success 'rebase two levels of merge' '
+       git checkout -b branch2 A1 &&
+       touch g &&
+       git add g &&
+       git commit -m G1 &&
+       git checkout -b branch3 &&
+       touch h
+       git add h &&
+       git commit -m H1 &&
+       git checkout -b branch4 &&
+       touch i &&
+       git add i &&
+       git commit -m I1 &&
+       git tag I1 &&
+       git checkout branch3 &&
+       touch j &&
+       git add j &&
+       git commit -m J1 &&
+       git merge I1 --no-commit &&
+       git commit -m K1 &&
+       git tag K1 &&
+       git checkout branch2 &&
+       touch l &&
+       git add l &&
+       git commit -m L1 &&
+       git merge K1 --no-commit &&
+       git commit -m M1 &&
+       GIT_EDITOR=: git rebase -i -p E2 &&
+       test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
+       test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
+       test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)"
+'
+
+test_done
index 0f27d7304964e071bbaa682b183a307013150789..c942c8be85339157e22f755d8fc94e64efaee4dd 100755 (executable)
@@ -90,7 +90,7 @@ test_expect_success \
     'validate file modification time' \
     'mkdir extract &&
      "$TAR" xf b.tar -C extract a/a &&
-     perl -e '\''print((stat("extract/a/a"))[9], "\n")'\'' >b.mtime &&
+     test-chmtime -v +0 extract/a/a |cut -f 1 >b.mtime &&
      echo "1117231200" >expected.mtime &&
      diff expected.mtime b.mtime'
 
index 344ab25b8b6ddcd8b687a72e43b9e26aec18263e..b0b0fdaca5e9ddfda6b1625545b9ba7049cf1555 100755 (executable)
@@ -11,13 +11,18 @@ test_expect_success \
     'rm -rf .git
      git init &&
      i=1 &&
-        while test $i -le 100
+     while test $i -le 100
      do
-                i=`printf '%03i' $i`
-         echo $i >file_$i &&
-         test-genrandom "$i" 8192 >>file_$i &&
-         git update-index --add file_$i &&
-                i=`expr $i + 1` || return 1
+         iii=`printf '%03i' $i`
+         test-genrandom "bar" 200 > wide_delta_$iii &&
+         test-genrandom "baz $iii" 50 >> wide_delta_$iii &&
+         test-genrandom "foo"$i 100 > deep_delta_$iii &&
+         test-genrandom "foo"`expr $i + 1` 100 >> deep_delta_$iii &&
+         test-genrandom "foo"`expr $i + 2` 100 >> deep_delta_$iii &&
+         echo $iii >file_$iii &&
+         test-genrandom "$iii" 8192 >>file_$iii &&
+         git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+         i=`expr $i + 1` || return 1
      done &&
      { echo 101 && test-genrandom 100 8192; } >file_101 &&
      git update-index --add file_101 &&
@@ -92,6 +97,31 @@ test_expect_success \
     '64-bit offsets: index-pack result should match pack-objects one' \
     'cmp "test-3-${pack3}.idx" "3.idx"'
 
+# returns the object number for given object in given pack index
+index_obj_nr()
+{
+    idx_file=$1
+    object_sha1=$2
+    nr=0
+    git show-index < $idx_file |
+    while read offs sha1 extra
+    do
+      nr=$(($nr + 1))
+      test "$sha1" = "$object_sha1" || continue
+      echo "$(($nr - 1))"
+      break
+    done
+}
+
+# returns the pack offset for given object as found in given pack index
+index_obj_offset()
+{
+    idx_file=$1
+    object_sha1=$2
+    git show-index < $idx_file | grep $object_sha1 |
+    ( read offs extra && echo "$offs" )
+}
+
 test_expect_success \
     '[index v1] 1) stream pack to repository' \
     'git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
@@ -102,19 +132,22 @@ test_expect_success \
 
 test_expect_success \
     '[index v1] 2) create a stealth corruption in a delta base reference' \
-    '# this test assumes a delta smaller than 16 bytes at the end of the pack
-     git show-index <1.idx | sort -n | sed -ne \$p | (
-       read delta_offs delta_sha1 &&
-       git cat-file blob "$delta_sha1" > blob_1 &&
-       chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
-       dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
-         if=".git/objects/pack/pack-${pack1}.idx" skip=$((256 * 4 + 4)) \
-         bs=1 count=20 conv=notrunc &&
-       git cat-file blob "$delta_sha1" > blob_2 )'
+    '# This test assumes file_101 is a delta smaller than 16 bytes.
+     # It should be against file_100 but we substitute its base for file_099
+     sha1_101=`git hash-object file_101` &&
+     sha1_099=`git hash-object file_099` &&
+     offs_101=`index_obj_offset 1.idx $sha1_101` &&
+     nr_099=`index_obj_nr 1.idx $sha1_099` &&
+     chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+     dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
+        if=".git/objects/pack/pack-${pack1}.idx" \
+        skip=$((4 + 256 * 4 + $nr_099 * 24)) \
+        bs=1 count=20 conv=notrunc &&
+     git cat-file blob $sha1_101 > file_101_foo1'
 
 test_expect_success \
     '[index v1] 3) corrupted delta happily returned wrong data' \
-    '! cmp blob_1 blob_2'
+    'test -f file_101_foo1 && ! cmp file_101 file_101_foo1'
 
 test_expect_success \
     '[index v1] 4) confirm that the pack is actually corrupted' \
@@ -140,19 +173,22 @@ test_expect_success \
 
 test_expect_success \
     '[index v2] 2) create a stealth corruption in a delta base reference' \
-    '# this test assumes a delta smaller than 16 bytes at the end of the pack
-     git show-index <1.idx | sort -n | sed -ne \$p | (
-       read delta_offs delta_sha1 delta_crc &&
-       git cat-file blob "$delta_sha1" > blob_3 &&
-       chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
-       dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
-         if=".git/objects/pack/pack-${pack1}.idx" skip=$((8 + 256 * 4)) \
-         bs=1 count=20 conv=notrunc &&
-       git cat-file blob "$delta_sha1" > blob_4 )'
+    '# This test assumes file_101 is a delta smaller than 16 bytes.
+     # It should be against file_100 but we substitute its base for file_099
+     sha1_101=`git hash-object file_101` &&
+     sha1_099=`git hash-object file_099` &&
+     offs_101=`index_obj_offset 1.idx $sha1_101` &&
+     nr_099=`index_obj_nr 1.idx $sha1_099` &&
+     chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+     dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
+        if=".git/objects/pack/pack-${pack1}.idx" \
+        skip=$((8 + 256 * 4 + $nr_099 * 20)) \
+        bs=1 count=20 conv=notrunc &&
+     git cat-file blob $sha1_101 > file_101_foo2'
 
 test_expect_success \
     '[index v2] 3) corrupted delta happily returned wrong data' \
-    '! cmp blob_3 blob_4'
+    'test -f file_101_foo2 && ! cmp file_101 file_101_foo2'
 
 test_expect_success \
     '[index v2] 4) confirm that the pack is actually corrupted' \
@@ -167,9 +203,11 @@ test_expect_success \
     'rm -f .git/objects/pack/* &&
      git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
      git verify-pack ".git/objects/pack/pack-${pack1}.pack" &&
+     obj=`git hash-object file_001` &&
+     nr=`index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj` &&
      chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
      dd if=/dev/zero of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
-        bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l <obj-list` * 20 + 0)) &&
+        bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l <obj-list` * 20 + $nr * 4)) &&
      ( while read obj
        do git cat-file -p $obj >/dev/null || exit 1
        done <obj-list ) &&
index 544771d8fa98ec70d84e5083c2bfc0ebdf45f9a1..6fe2f87b850c1a2d689d5a831084da8915c74ea2 100755 (executable)
@@ -103,6 +103,17 @@ unset GIT_CONFIG GIT_CONFIG_LOCAL
 HOME=`pwd`/no-such-directory
 export HOME ;# this way we force the victim/.git/config to be used.
 
+test_expect_failure \
+       '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
+'
+rm -f victim/.git/refs/heads/extra
+
 test_expect_success \
         'pushing with --force should be denied with denyNonFastforwards' '
        cd victim &&
diff --git a/t/t8005-blame-i18n.sh b/t/t8005-blame-i18n.sh
new file mode 100755 (executable)
index 0000000..4470a92
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='git blame encoding conversion'
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/t8005/utf8.txt
+. "$TEST_DIRECTORY"/t8005/cp1251.txt
+. "$TEST_DIRECTORY"/t8005/sjis.txt
+
+test_expect_success 'setup the repository' '
+       # Create the file
+       echo "UTF-8 LINE" > file &&
+       git add file &&
+       git commit --author "$UTF8_NAME <utf8@localhost>" -m "$UTF8_MSG" &&
+
+       echo "CP1251 LINE" >> file &&
+       git add file &&
+       git config i18n.commitencoding cp1251 &&
+       git commit --author "$CP1251_NAME <cp1251@localhost>" -m "$CP1251_MSG" &&
+
+       echo "SJIS LINE" >> file &&
+       git add file &&
+       git config i18n.commitencoding shift-jis &&
+       git commit --author "$SJIS_NAME <sjis@localhost>" -m "$SJIS_MSG"
+'
+
+cat >expected <<EOF
+author $SJIS_NAME
+summary $SJIS_MSG
+author $SJIS_NAME
+summary $SJIS_MSG
+author $SJIS_NAME
+summary $SJIS_MSG
+EOF
+
+test_expect_success \
+       'blame respects i18n.commitencoding' '
+       git blame --incremental file | \
+               grep "^\(author\|summary\) " > actual &&
+       test_cmp actual expected
+'
+
+cat >expected <<EOF
+author $CP1251_NAME
+summary $CP1251_MSG
+author $CP1251_NAME
+summary $CP1251_MSG
+author $CP1251_NAME
+summary $CP1251_MSG
+EOF
+
+test_expect_success \
+       'blame respects i18n.logoutputencoding' '
+       git config i18n.logoutputencoding cp1251 &&
+       git blame --incremental file | \
+               grep "^\(author\|summary\) " > actual &&
+       test_cmp actual expected
+'
+
+cat >expected <<EOF
+author $UTF8_NAME
+summary $UTF8_MSG
+author $UTF8_NAME
+summary $UTF8_MSG
+author $UTF8_NAME
+summary $UTF8_MSG
+EOF
+
+test_expect_success \
+       'blame respects --encoding=utf-8' '
+       git blame --incremental --encoding=utf-8 file | \
+               grep "^\(author\|summary\) " > actual &&
+       test_cmp actual expected
+'
+
+cat >expected <<EOF
+author $SJIS_NAME
+summary $SJIS_MSG
+author $CP1251_NAME
+summary $CP1251_MSG
+author $UTF8_NAME
+summary $UTF8_MSG
+EOF
+
+test_expect_success \
+       'blame respects --encoding=none' '
+       git blame --incremental --encoding=none file | \
+               grep "^\(author\|summary\) " > actual &&
+       test_cmp actual expected
+'
+
+test_done
diff --git a/t/t8005/cp1251.txt b/t/t8005/cp1251.txt
new file mode 100644 (file)
index 0000000..ce41e98
--- /dev/null
@@ -0,0 +1,2 @@
+CP1251_NAME="Èâàí Ïåòðîâè÷ Ñèäîðîâ"
+CP1251_MSG="Òåñòîâîå ñîîáùåíèå"
diff --git a/t/t8005/sjis.txt b/t/t8005/sjis.txt
new file mode 100644 (file)
index 0000000..2ccfbad
--- /dev/null
@@ -0,0 +1,2 @@
+SJIS_NAME="\84I\84r\84p\84\84P\84u\84\84\84\82\84\80\84r\84y\84\89 \84R\84y\84t\84\80\84\82\84\80\84r"
+SJIS_MSG="\84S\84u\84\83\84\84\84\80\84r\84\80\84\84\83\84\80\84\80\84q\84\8b\84u\84~\84y\84u"
diff --git a/t/t8005/utf8.txt b/t/t8005/utf8.txt
new file mode 100644 (file)
index 0000000..f46cfc5
--- /dev/null
@@ -0,0 +1,2 @@
+UTF8_NAME="Иван Петрович Сидоров"
+UTF8_MSG="Тестовое сообщение"
index 90da448ebec3e5375b7725ba7f297c1c74199b87..d5358cbaac2022483b74366555fc9707a7d8ad97 100644 (file)
@@ -1,39 +1,83 @@
+/*
+ * This program can either change modification time of the given
+ * file(s) or just print it. The program does not change atime nor
+ * ctime (their values are explicitely preserved).
+ *
+ * The mtime can be changed to an absolute value:
+ *
+ *     test-chmtime =<seconds> file...
+ *
+ * Relative to the current time as returned by time(3):
+ *
+ *     test-chmtime =+<seconds> (or =-<seconds>) file...
+ *
+ * Or relative to the current mtime of the file:
+ *
+ *     test-chmtime <seconds> file...
+ *     test-chmtime +<seconds> (or -<seconds>) file...
+ *
+ * Examples:
+ *
+ * To just print the mtime use --verbose and set the file mtime offset to 0:
+ *
+ *     test-chmtime -v +0 file
+ *
+ * To set the mtime to current time:
+ *
+ *     test-chmtime =+0 file
+ *
+ */
 #include "git-compat-util.h"
 #include <utime.h>
 
-static const char usage_str[] = "(+|=|=+|=-|-)<seconds> <file>...";
+static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>...";
 
-int main(int argc, const char *argv[])
+static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
 {
-       int i;
-       int set_eq;
-       long int set_time;
        char *test;
-       const char *timespec;
-
-       if (argc < 3)
-               goto usage;
-
-       timespec = argv[1];
-       set_eq = (*timespec == '=') ? 1 : 0;
-       if (set_eq) {
+       const char *timespec = arg;
+       *set_eq = (*timespec == '=') ? 1 : 0;
+       if (*set_eq) {
                timespec++;
                if (*timespec == '+') {
-                       set_eq = 2; /* relative "in the future" */
+                       *set_eq = 2; /* relative "in the future" */
                        timespec++;
                }
        }
-       set_time = strtol(timespec, &test, 10);
+       *set_time = strtol(timespec, &test, 10);
        if (*test) {
-               fprintf(stderr, "Not a base-10 integer: %s\n", argv[1] + 1);
-               goto usage;
+               fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1);
+               return 0;
        }
-       if ((set_eq && set_time < 0) || set_eq == 2) {
+       if ((*set_eq && *set_time < 0) || *set_eq == 2) {
                time_t now = time(NULL);
-               set_time += now;
+               *set_time += now;
        }
+       return 1;
+}
+
+int main(int argc, const char *argv[])
+{
+       static int verbose;
 
-       for (i = 2; i < argc; i++) {
+       int i = 1;
+       /* no mtime change by default */
+       int set_eq = 0;
+       long int set_time = 0;
+
+       if (argc < 3)
+               goto usage;
+
+       if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
+               verbose = 1;
+               ++i;
+       }
+       if (timespec_arg(argv[i], &set_time, &set_eq))
+               ++i;
+       else
+               goto usage;
+
+       for (; i < argc; i++) {
                struct stat sb;
                struct utimbuf utb;
 
@@ -46,7 +90,12 @@ int main(int argc, const char *argv[])
                utb.actime = sb.st_atime;
                utb.modtime = set_eq ? set_time : sb.st_mtime + set_time;
 
-               if (utime(argv[i], &utb) < 0) {
+               if (verbose) {
+                       uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime;
+                       printf("%"PRIuMAX"\t%s\n", mtime, argv[i]);
+               }
+
+               if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) {
                        fprintf(stderr, "Failed to modify time on %s: %s\n",
                                argv[i], strerror(errno));
                        return -1;
index e59d144d28164f2451784513105f6269f0e9167c..e5749ef638b4a9a490894d9d4fc5876d904ab9ac 100644 (file)
@@ -382,7 +382,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        o->merge_size = len;
 
        if (!dfc)
-               dfc = xcalloc(1, sizeof(struct cache_entry) + 1);
+               dfc = xcalloc(1, cache_entry_size(0));
        o->df_conflict_entry = dfc;
 
        if (len) {