diff options
392 files changed, 37189 insertions, 11322 deletions
diff --git a/.gitignore b/.gitignore index 63c918c667..c8c13f5503 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ git-clone git-commit git-commit-tree git-config -git-convert-objects git-count-objects git-cvsexportcommit git-cvsimport @@ -129,7 +128,6 @@ git-status git-stripspace git-submodule git-svn -git-svnimport git-symbolic-ref git-tag git-tar-tree @@ -155,6 +153,7 @@ test-delta test-dump-cache-tree test-genrandom test-match-trees +test-parse-options test-sha1 common-cmds.h *.tar.gz @@ -172,3 +171,6 @@ config.status config.mak.autogen config.mak.append configure +tags +TAGS +cscope* @@ -37,6 +37,7 @@ Sam Vilain <sam@vilain.net> Santi Béjar <sbejar@gmail.com> Sean Estabrooks <seanlkml@sympatico.ca> Shawn O. Pearce <spearce@spearce.org> +Steven Grimm <koreth@midwinter.com> Theodore Ts'o <tytso@mit.edu> Tony Luck <tony.luck@intel.com> Uwe Kleine-König <Uwe_Zeisberger@digi.com> diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt new file mode 100644 index 0000000000..c9c537649f --- /dev/null +++ b/Documentation/RelNotes-1.5.4.txt @@ -0,0 +1,154 @@ +GIT v1.5.4 Release Notes +======================== + +Updates since v1.5.3 +-------------------- + + * Comes with much improved gitk. + + * Comes with git-gui 0.9.0 with i18n. + + * git-lost-found was deprecated in favor of git-fsck's --lost-found + option. + + * git-peek-remote is deprecated, as git-ls-remote was written in C and + works for all transports. + + * "progress display" from many commands are a lot nicer to the + eye. Transfer commands show throughput data. + + * git-reset is now built-in and its output can be squelched with -q. + + * git-send-email can optionally talk over ssmtp and use SMTP-AUTH. + + * git-rebase learned --whitespace option. + + * In git-rebase, when you decide not to replay a particular change + after the command stopped with a conflict, you can say "git-rebase + --skip" without first running "git reset --hard", as the command now + runs it for you. + + * git-remote knows --mirror mode. + + * git-merge can call the "post-merge" hook. + + * git-pack-objects can optionally run deltification with multiple threads. + + * git-archive can optionally substitute keywords in files marked with + export-subst attribute. + + * git-for-each-ref learned %(xxxdate:<dateformat>) syntax to + show the various date fields in different formats. + + * git-gc --auto is a low-impact way to automatically run a + variant of git-repack that does not lose unreferenced objects + (read: safer than the usual one) after the user accumulates + too many loose objects. + + * You need to explicitly set clean.requireForce to "false" to allow + git-clean without -f to do any damage (lack of the configuration + variable used to mean "do not require", but we now use the safer + default). + + * git-clean has been rewritten in C. + + * git-push has been rewritten in C. + + * git-push learned --dry-run option to show what would happen + if a push is run. + + * git-push does not update a tracking ref on the pushing side when the + remote refused to update the corresponding ref. + + * git-push learned --mirror option. This is to push the local refs + one-to-one to the remote, and deletes refs from the remote that do + not exist anymore in the repository on the pushing side. + + * git-remote learned "rm" subcommand. + + * git-rebase --interactive mode can now work on detached HEAD. + + * git-cvsserver can be run via git-shell. + + * git-am and git-rebase are far less verbose. + + * git-pull learned to pass --[no-]ff option to underlying git-merge. + + * Various Perforce importer updates. + + * "git log" learned --early-output option to help interactive + GUI implementations. + + * git-svnimport was removed in favor of git-svn. + + * git-bisect learned "skip" action to mark untestable commits. + + * git-format-patch learned "format.numbered" configuration variable + to automatically turn --numbered option on when more than one + commits are formatted. + + * git-ls-files learned "--exclude-standard" to use the canned + set of exclude files. + + * git-rebase now detaches head during its operation, so after a + successful "git rebase" operation, the reflog entry branch@{1} + for the current branch points at the commit before the rebase + was started. + + * "git-tag -a -f existing" begins the editor session using the + existing annotation message. + + * "git cvsexportcommit" learned -w option to specify and switch + to the CVS working directory. + + * "git checkout" from a subdirectory learned to use "../path" + to allow checking out a path outside the current directory + without cd'ing up. + + * "git send-email --dry-run" shows full headers for easier + diagnosis. + + * "git merge-ours" is built-in. + + * "git svn" learned "info" subcommand. + + * "git status" from a subdirectory now shows relative paths + which makes copy-and-pasting for git-checkout/git-add/git-rm + easier. + + * Output processing for '--pretty=format:<user format>' has + been optimized. + + * Rename detection diff family, while detecting exact matches, + has been greatly optimized. + + * Example update and post-receive hooks have been improved. + + * In addition there are quite a few internal clean-ups. Notably + + - many fork/exec have been replaced with run-command API, + brought from the msysgit effort. + + - introduction and more use of the option parser API. + + - enhancement and more use of the strbuf API. + + +Fixes since v1.5.3 +------------------ + +All of the fixes in v1.5.3 maintenance series are included in +this release, unless otherwise noted. + + * git-svn talking with the SVN over http will correctly quote branch + and project names. + + * "git rev-list --objects A..B" choked when the lower boundary + of the range involved a subproject. This fix is also queued + for 'maint' (but not in there yet). + +-- +exec >/var/tmp/1 +O=v1.5.3.6-950-gda03a58 +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 4ee76eaf99..b709551726 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -3,7 +3,8 @@ use File::Compare qw(compare); sub format_one { - my ($out, $name) = @_; + my ($out, $nameattr) = @_; + my ($name, $attr) = @$nameattr; my ($state, $description); $state = 0; open I, '<', "$name.txt" or die "No such file $name.txt"; @@ -26,8 +27,11 @@ sub format_one { die "No description found in $name.txt"; } if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) { - print $out "gitlink:$name\[1\]::\n"; - print $out "\t$text.\n\n"; + print $out "gitlink:$name\[1\]::\n\t"; + if ($attr) { + print $out "($attr) "; + } + print $out "$text.\n\n"; } else { die "Description does not match $name: $description"; @@ -39,8 +43,8 @@ while (<DATA>) { next if /^#/; chomp; - my ($name, $cat) = /^(\S+)\s+(.*)$/; - push @{$cmds{$cat}}, $name; + my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/; + push @{$cmds{$cat}}, [$name, $attr]; } for my $cat (qw(ancillaryinterrogators @@ -94,7 +98,6 @@ git-clone mainporcelain git-commit mainporcelain git-commit-tree plumbingmanipulators git-config ancillarymanipulators -git-convert-objects ancillarymanipulators git-count-objects ancillaryinterrogators git-cvsexportcommit foreignscminterface git-cvsimport foreignscminterface @@ -125,9 +128,8 @@ git-index-pack plumbingmanipulators git-init mainporcelain git-instaweb ancillaryinterrogators gitk mainporcelain -git-local-fetch synchingrepositories git-log mainporcelain -git-lost-found ancillarymanipulators +git-lost-found ancillarymanipulators deprecated git-ls-files plumbinginterrogators git-ls-remote plumbinginterrogators git-ls-tree plumbinginterrogators @@ -149,7 +151,7 @@ git-pack-redundant plumbinginterrogators git-pack-refs ancillarymanipulators git-parse-remote synchelpers git-patch-id purehelpers -git-peek-remote purehelpers +git-peek-remote purehelpers deprecated git-prune ancillarymanipulators git-prune-packed plumbingmanipulators git-pull mainporcelain @@ -179,17 +181,14 @@ git-show-branch ancillaryinterrogators git-show-index plumbinginterrogators git-show-ref plumbinginterrogators git-sh-setup purehelpers -git-ssh-fetch synchingrepositories -git-ssh-upload synchingrepositories git-stash mainporcelain git-status mainporcelain git-stripspace purehelpers git-submodule mainporcelain git-svn foreignscminterface -git-svnimport foreignscminterface git-symbolic-ref plumbingmanipulators git-tag mainporcelain -git-tar-tree plumbinginterrogators +git-tar-tree plumbinginterrogators deprecated git-unpack-file plumbinginterrogators git-unpack-objects plumbingmanipulators git-update-index plumbingmanipulators diff --git a/Documentation/config.txt b/Documentation/config.txt index 83bc33c421..39d1ef5298 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -188,7 +188,7 @@ core.worktree:: Set the path to the working tree. The value will not be used in combination with repositories found automatically in a .git directory (i.e. $GIT_DIR is not set). - This can be overriden by the GIT_WORK_TREE environment + This can be overridden by the GIT_WORK_TREE environment variable and the '--work-tree' command line option. core.logAllRefUpdates:: @@ -326,10 +326,11 @@ branch.<name>.remote:: If this option is not given, `git fetch` defaults to remote "origin". branch.<name>.merge:: - When in branch <name>, it tells `git fetch` the default refspec to - be marked for merging in FETCH_HEAD. The value has exactly to match - a remote part of one of the refspecs which are fetched from the remote - given by "branch.<name>.remote". + When in branch <name>, it tells `git fetch` the default + refspec to be marked for merging in FETCH_HEAD. The value is + handled like the remote part of a refspec, and must match a + ref which is fetched from the remote given by + "branch.<name>.remote". The merge information is used by `git pull` (which at first calls `git fetch`) to lookup the default branch for merging. Without this option, `git pull` defaults to merge the first refspec fetched. @@ -339,9 +340,15 @@ branch.<name>.merge:: branch.<name>.merge to the desired branch, and use the special setting `.` (a period) for branch.<name>.remote. +branch.<name>.mergeoptions:: + Sets default options for merging into branch <name>. The syntax and + supported options are equal to that of gitlink:git-merge[1], but + option values containing whitespace characters are currently not + supported. + clean.requireForce:: - A boolean to make git-clean do nothing unless given -f or -n. Defaults - to false. + A boolean to make git-clean do nothing unless given -f + or -n. Defaults to true. color.branch:: A boolean to enable/disable color in the output of @@ -427,6 +434,12 @@ fetch.unpackLimit:: pack from a push can make the push operation complete faster, especially on slow filesystems. +format.numbered:: + A boolean which can enable sequence numbers in patch subjects. + Seting this option to "auto" will enable it only if there is + more than one patch. See --numbered option in + gitlink:git-format-patch[1]. + format.headers:: Additional email headers to include in a patch to be submitted by mail. See gitlink:git-format-patch[1]. @@ -441,6 +454,19 @@ gc.aggressiveWindow:: algorithm used by 'git gc --aggressive'. This defaults to 10. +gc.auto:: + When there are approximately more than this many loose + objects in the repository, `git gc --auto` will pack them. + Some Porcelain commands use this command to perform a + light-weight garbage collection from time to time. Setting + this to 0 disables it. + +gc.autopacklimit:: + When there are more than this many packs that are not + marked with `*.keep` file in the repository, `git gc + --auto` consolidates them into one larger pack. Setting + this to 0 disables this. + gc.packrefs:: `git gc` does not run `git pack-refs` in a bare repository by default so that older dumb-transport clients can still fetch @@ -590,7 +616,7 @@ merge.verbosity:: message if conflicts were detected. Level 1 outputs only conflicts, 2 outputs conflicts and file changes. Level 5 and above outputs debugging information. The default is level 2. - Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable. + Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. merge.<driver>.name:: Defines a human readable name for a custom low-level @@ -634,9 +660,26 @@ pack.deltaCacheSize:: A value of 0 means no limit. Defaults to 0. pack.deltaCacheLimit:: - The maxium size of a delta, that is cached in + The maximum size of a delta, that is cached in gitlink:git-pack-objects[1]. Defaults to 1000. +pack.threads:: + Specifies the number of threads to spawn when searching for best + delta matches. This requires that gitlink:git-pack-objects[1] + be compiled with pthreads otherwise this option is ignored with a + warning. This is meant to reduce packing time on multiprocessor + machines. The required amount of memory for the delta search window + is however multiplied by the number of threads. + +pack.indexVersion:: + Specify the default pack index version. Valid values are 1 for + legacy pack index used by Git versions prior to 1.5.2, and 2 for + the new pack index with capabilities for packs larger than 4 GB + as well as proper protection against the repacking of corrupted + packs. Version 2 is selected and this config option ignored + whenever the corresponding pack is larger than 2 GB. Otherwise + the default is 1. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index c3f0be535d..bd6cd41245 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -553,13 +553,8 @@ can explore on your own. [NOTE] Most likely, you are not directly using the core -git Plumbing commands, but using Porcelain like Cogito on top -of it. Cogito works a bit differently and you usually do not -have to run `git-update-index` yourself for changed files (you -do tell underlying git about additions and removals via -`cg-add` and `cg-rm` commands). Just before you make a commit -with `cg-commit`, Cogito figures out which files you modified, -and runs `git-update-index` on them for you. +git Plumbing commands, but using Porcelain such as `git-add`, `git-rm' +and `git-commit'. Tagging a version @@ -686,8 +681,8 @@ $ git reset and in fact a lot of the common git command combinations can be scripted with the `git xyz` interfaces. You can learn things by just looking -at what the various git scripts do. For example, `git reset` is the -above two lines implemented in `git-reset`, but some things like +at what the various git scripts do. For example, `git reset` used to be +the above two lines implemented in `git-reset`, but some things like `git status` and `git commit` are slightly more complex scripts around the basic git commands. @@ -805,8 +800,8 @@ you have, you can say $ git branch ------------ -which is nothing more than a simple script around `ls .git/refs/heads`. -There will be asterisk in front of the branch you are currently on. +which used to be nothing more than a simple script around `ls .git/refs/heads`. +There will be an asterisk in front of the branch you are currently on. Sometimes you may wish to create a new branch _without_ actually checking it out and switching to it. If so, just use the command @@ -936,12 +931,13 @@ Another useful tool, especially if you do not always work in X-Window environment, is `git show-branch`. ------------------------------------------------ -$ git show-branch --topo-order master mybranch +$ git-show-branch --topo-order --more=1 master mybranch * [master] Merge work in mybranch ! [mybranch] Some work. -- - [master] Merge work in mybranch *+ [mybranch] Some work. +* [master^] Some fun. ------------------------------------------------ The first two lines indicate that it is showing the two branches @@ -952,17 +948,29 @@ the later output lines is used to show commits contained in the `master` branch, and the second column for the `mybranch` branch. Three commits are shown along with their log messages. All of them have non blank characters in the first column (`*` -shows an ordinary commit on the current branch, `.` is a merge commit), which +shows an ordinary commit on the current branch, `-` is a merge commit), which means they are now part of the `master` branch. Only the "Some work" commit has the plus `+` character in the second column, because `mybranch` has not been merged to incorporate these commits from the master branch. The string inside brackets before the commit log message is a short name you can use to name the commit. In the above example, 'master' and 'mybranch' -are branch heads. 'master~1' is the first parent of 'master' +are branch heads. 'master^' is the first parent of 'master' branch head. Please see 'git-rev-parse' documentation if you see more complex cases. +[NOTE] +Without the '--more=1' option, 'git-show-branch' would not output the +'[master^]' commit, as '[mybranch]' commit is a common ancestor of +both 'master' and 'mybranch' tips. Please see 'git-show-branch' +documentation for details. + +[NOTE] +If there were more commits on the 'master' branch after the merge, the +merge commit itself would not be shown by 'git-show-branch' by +default. You would need to provide '--sparse' option to make the +merge commit visible in this case. + Now, let's pretend you are the one who did all the work in `mybranch`, and the fruit of your hard work has finally been merged to the `master` branch. Let's go back to `mybranch`, and run @@ -1188,7 +1196,7 @@ $ mb=$(git-merge-base HEAD mybranch) The command writes the commit object name of the common ancestor to the standard output, so we captured its output to a variable, -because we will be using it in the next step. BTW, the common +because we will be using it in the next step. By the way, the common ancestor commit is the "New day." commit in this case. You can tell it by: @@ -1454,8 +1462,7 @@ Although git is a truly distributed system, it is often convenient to organize your project with an informal hierarchy of developers. Linux kernel development is run this way. There is a nice illustration (page 17, "Merges to Mainline") in -link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf -[Randy Dunlap's presentation]. +link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation]. It should be stressed that this hierarchy is purely *informal*. There is nothing fundamental in git that enforces the "chain of diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b1f528ae88..e4af393515 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -1,5 +1,25 @@ +// Please don't remove this comment as asciidoc behaves badly when +// the first non-empty line is ifdef/ifndef. The symptom is that +// without this comment the <git-diff-core> attribute conditionally +// defined below ends up being defined unconditionally. +// Last checked with asciidoc 7.0.2. + +ifndef::git-format-patch[] +ifndef::git-diff[] +:git-diff-core: 1 +endif::git-diff[] +endif::git-format-patch[] + +ifdef::git-format-patch[] -p:: - Generate patch (see section on generating patches) + Generate patches without diffstat. +endif::git-format-patch[] + +ifndef::git-format-patch[] +-p:: + Generate patch (see section on generating patches). + {git-diff? This is the default.} +endif::git-format-patch[] -u:: Synonym for "-p". @@ -13,6 +33,7 @@ --raw:: Generate the raw format. + {git-diff-core? This is the default.} --patch-with-raw:: Synonym for "-p --raw". @@ -41,6 +62,7 @@ --patch-with-stat:: Synonym for "-p --stat". + {git-format-patch? This is the default.} -z:: NUL-line termination on output. This affects the --raw diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index fd82fc19b5..63829d93cc 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -50,10 +50,10 @@ OPTIONS and `dir/file2`) can be given to add all files in the directory, recursively. --n:: +-n, \--dry-run:: Don't actually add the file(s), just show if they exist. --v:: +-v, \--verbose:: Be verbose. -f:: diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 1072fb87d1..4795349c10 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending on the subcommand: git bisect start [<bad> [<good>...]] [--] [<paths>...] - git bisect bad <rev> - git bisect good <rev> + git bisect bad [<rev>] + git bisect good [<rev>...] + git bisect skip [<rev>...] git bisect reset [<branch>] git bisect visualize git bisect replay <logfile> @@ -134,6 +135,20 @@ $ git reset --hard HEAD~3 # try 3 revs before what Then compile and test the one you chose to try. After that, tell bisect what the result was as usual. +Bisect skip +~~~~~~~~~~~~ + +Instead of choosing by yourself a nearby commit, you may just want git +to do it for you using: + +------------ +$ git bisect skip # Current version cannot be tested +------------ + +But computing the commit to test may be slower afterwards and git may +eventually not be able to tell the first bad among a bad and one or +more "skip"ped commits. + Cutting down bisection by giving more parameters to bisect start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,14 +182,18 @@ $ git bisect run my_script ------------ Note that the "run" script (`my_script` in the above example) should -exit with code 0 in case the current source code is good and with a -code between 1 and 127 (included) in case the current source code is -bad. +exit with code 0 in case the current source code is good. Exit with a +code between 1 and 127 (inclusive), except 125, if the current +source code is bad. Any other exit code will abort the automatic bisect process. (A program that does "exit(-1)" leaves $? = 255, see exit(3) manual page, the value is chopped with "& 0377".) +The special exit code 125 should be used when the current source code +cannot be tested. If the "run" script exits with this code, the current +revision will be skipped, see `git bisect skip` above. + You may often find that during bisect you want to have near-constant tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or "revision that does not have this commit needs this patch applied to diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index d64ad25028..f87b6968b4 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -90,7 +90,7 @@ OPTIONS -a:: List both remote-tracking branches and local branches. --v:: +-v, --verbose:: Show sha1 and commit subject line for each head. --abbrev=<length>:: diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 47b1e8c2fc..937c4a7926 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit SYNOPSIS -------- -'git-cherry-pick' [--edit] [-n] [-x] <commit> +'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] <commit> DESCRIPTION ----------- @@ -27,11 +27,12 @@ OPTIONS message prior committing. -x:: - Cause the command to append which commit was - cherry-picked after the original commit message when - making a commit. Do not use this option if you are - cherry-picking from your private branch because the - information is useless to the recipient. If on the + When recording the commit, append to the original commit + message a note that indicates which commit this change + was cherry-picked from. Append the note only for cherry + picks without conflicts. Do not use this option if + you are cherry-picking from your private branch because + the information is useless to the recipient. If on the other hand you are cherry-picking between two publicly visible branches (e.g. backporting a fix to a maintenance branch for an older release from a @@ -43,6 +44,13 @@ OPTIONS described above, and `-r` was to disable it. Now the default is not to do `-x` so this option is a no-op. +-m parent-number|--mainline parent-number:: + Usually you cannot revert a merge because you do not know which + side of the merge should be considered the mainline. This + option specifies the parent number (starting from 1) of + the mainline and allows cherry-pick to replay the change + relative to the specified parent. + -n|--no-commit:: Usually the command automatically creates a commit with a commit log message stating which commit was diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index cca14d6b5d..c90bcece24 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git-clone' [--template=<template_directory>] [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [-o <name>] [-u <upload-pack>] [--reference <repository>] - [--depth <depth>] <repository> [<directory>] + [--depth <depth>] [--] <repository> [<directory>] DESCRIPTION ----------- @@ -130,6 +130,7 @@ OPTIONS for "host.xz:foo/.git"). Cloning into an existing directory is not allowed. +:git-clone: 1 include::urls.txt[] Examples diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index c3922f9238..3f9d2295d3 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION @@ -16,8 +16,9 @@ DESCRIPTION Exports a commit from GIT to a CVS checkout, making it easier to merge patches from a git repository into a CVS repository. -Execute it from the root of the CVS working copy. GIT_DIR must be defined. -See examples below. +Specify the name of a CVS checkout using the -w switch or execute it +from the root of the CVS working copy. In the latter case GIT_DIR must +be defined. See examples below. It does its best to do the safe thing, it will check that the files are unchanged and up to date in the CVS checkout, and it will not autocommit @@ -61,6 +62,11 @@ OPTIONS -u:: Update affected files from CVS repository before attempting export. +-w:: + Specify the location of the CVS checkout to use for the export. This + option does not require GIT_DIR to be set before execution if the + current directory is within a git repository. + -v:: Verbose. @@ -76,6 +82,12 @@ $ git-cvsexportcommit -v <commit-sha1> $ cvs commit -F .msg <files> ------------ +Merge one patch into CVS (-c and -w options). The working directory is within the Git Repo:: ++ +------------ + $ git-cvsexportcommit -v -c -w ~/project_cvs_checkout <commit-sha1> +------------ + Merge pending patches into CVS automatically -- only if you really know what you are doing:: + ------------ @@ -86,11 +98,11 @@ $ git-cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git-cvsexportcommit Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz> +Written by Martin Langhoff <martin@catalyst.net.nz> and others. Documentation -------------- -Documentation by Martin Langhoff <martin@catalyst.net.nz> +Documentation by Martin Langhoff <martin@catalyst.net.nz> and others. GIT --- diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 201d5daf1a..2808a5ec44 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -75,6 +75,7 @@ and the range notations ("<commit>..<commit>" and OPTIONS ------- +:git-diff: 1 include::diff-options.txt[] <path>...:: @@ -128,7 +129,7 @@ $ git diff topic...master <3> + <1> Changes between the tips of the topic and the master branches. <2> Same as above. -<3> Changes that occured on the master branch since when the topic +<3> Changes that occurred on the master branch since when the topic branch was started off it. Limiting the diff output:: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 6df8e85004..f1f90cca62 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -100,6 +100,11 @@ In any case, a field name that refers to a field inapplicable to the object referred by the ref does not cause an error. It returns an empty string instead. +As a special case for the date-type fields, you may specify a format for +the date by adding one of `:default`, `:relative`, `:short`, `:local`, +`:iso8601` or `:rfc2822` to the end of the fieldname; e.g. +`%(taggerdate:relative)`. + EXAMPLES -------- diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index f0617efa0a..6fb9429851 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -9,9 +9,10 @@ git-format-patch - Prepare patches for e-mail submission SYNOPSIS -------- [verse] -'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread] +'git-format-patch' [-k] [-o <dir> | --stdout] [--thread] [--attach[=<boundary>] | --inline[=<boundary>]] [-s | --signoff] [<common diff options>] + [-n | --numbered | -N | --no-numbered] [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] [--ignore-if-in-upstream] @@ -65,6 +66,7 @@ reference. OPTIONS ------- +:git-format-patch: 1 include::diff-options.txt[] -<n>:: @@ -77,6 +79,9 @@ include::diff-options.txt[] -n|--numbered:: Name output in '[PATCH n/m]' format. +-N|--no-numbered:: + Name output in '[PATCH]' format. + --start-number <n>:: Start numbering the patches at <n> instead of 1. @@ -142,15 +147,16 @@ not add any suffix. CONFIGURATION ------------- -You can specify extra mail header lines to be added to each -message in the repository configuration. You can also specify -new defaults for the subject prefix and file suffix. +You can specify extra mail header lines to be added to each message +in the repository configuration, new defaults for the subject prefix +and file suffix, and number patches when outputting more than one. ------------ [format] headers = "Organization: git-foo\n" subjectprefix = CHANGE suffix = .txt + numbered = auto ------------ diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index c7742ca963..872056ea04 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository SYNOPSIS -------- -'git-gc' [--prune] [--aggressive] +'git-gc' [--prune] [--aggressive] [--auto] DESCRIPTION ----------- @@ -19,7 +19,8 @@ created from prior invocations of gitlink:git-add[1]. Users are encouraged to run this task on a regular basis within each repository to maintain good disk space utilization and good -operating performance. +operating performance. Some git commands may automatically run +`git-gc`; see the `--auto` flag below for details. OPTIONS ------- @@ -43,6 +44,25 @@ OPTIONS persistent, so this option only needs to be used occasionally; every few hundred changesets or so. +--auto:: + With this option, `git gc` checks whether any housekeeping is + required; if not, it exits without performing any work. + Some git commands run `git gc --auto` after performing + operations that could create many loose objects. ++ +Housekeeping is required if there are too many loose objects or +too many packs in the repository. If the number of loose objects +exceeds the value of the `gc.auto` configuration variable, then +all loose objects are combined into a single pack using +`git-repack -d -l`. Setting the value of `gc.auto` to 0 +disables automatic packing of loose objects. ++ +If the number of packs exceeds the value of `gc.autopacklimit`, +then existing packs (except those marked with a `.keep` file) +are consolidated into a single pack by using the `-A` option of +`git-repack`. Setting `gc.autopacklimit` to 0 disables +automatic consolidation of packs. + Configuration ------------- diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index 9afb860381..3a69b719b5 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -8,7 +8,7 @@ git-http-push - Push objects over HTTP/DAV to another repository SYNOPSIS -------- -'git-http-push' [--all] [--force] [--verbose] <url> <ref> [<ref>...] +'git-http-push' [--all] [--dry-run] [--force] [--verbose] <url> <ref> [<ref>...] DESCRIPTION ----------- @@ -30,6 +30,9 @@ OPTIONS the remote repository can lose commits; use it with care. +--dry-run:: + Do everything except actually send the updates. + --verbose:: Report the list of objects being walked locally and the list of objects successfully sent to the remote repository. diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index a8a7f6f04b..bf5c2bddf4 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -43,7 +43,7 @@ OPTIONS a default name determined from the pack content. If <pack-file> is not specified consider using --keep to prevent a race condition between this process and - gitlink::git-repack[1] . + gitlink::git-repack[1]. --fix-thin:: It is possible for gitlink:git-pack-objects[1] to build diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index cec60ee780..735008c1ab 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -27,7 +27,7 @@ OPTIONS The HTTP daemon command-line that will be executed. Command-line options may be specified here, and the configuration file will be added at the end of the command-line. - Currently, lighttpd and apache2 are the only supported servers. + Currently lighttpd, apache2 and webrick are supported. (Default: lighttpd) -m|--module-path:: diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt deleted file mode 100644 index e830deeff3..0000000000 --- a/Documentation/git-local-fetch.txt +++ /dev/null @@ -1,66 +0,0 @@ -git-local-fetch(1) -================== - -NAME ----- -git-local-fetch - Duplicate another git repository on a local system - - -SYNOPSIS --------- -[verse] -'git-local-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] - commit-id path - -DESCRIPTION ------------ -THIS COMMAND IS DEPRECATED. - -Duplicates another git repository on a local system. - -OPTIONS -------- --c:: - Get the commit objects. --t:: - Get trees associated with the commit objects. --a:: - Get all the objects. --v:: - Report what is downloaded. --s:: - Instead of regular file-to-file copying use symbolic links to the objects - in the remote repository. --l:: - Before attempting symlinks (if -s is specified) or file-to-file copying the - remote objects, try to hardlink the remote objects into the local - repository. --n:: - Never attempt to file-to-file copy remote objects. Only useful with - -s or -l command-line options. - --w <filename>:: - Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on - the local end after the transfer is complete. - ---stdin:: - Instead of a commit id on the command line (which is not expected in this - case), 'git-local-fetch' expects lines on stdin in the format - - <commit-id>['\t'<filename-as-in--w>] - ---recover:: - Verify that everything reachable from target is fetched. Used after - an earlier fetch is interrupted. - -Author ------- -Written by Junio C Hamano <junkio@cox.net> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt index bc739117be..7f808fcd76 100644 --- a/Documentation/git-lost-found.txt +++ b/Documentation/git-lost-found.txt @@ -11,6 +11,10 @@ SYNOPSIS DESCRIPTION ----------- + +*NOTE*: this command is deprecated. Use gitlink:git-fsck[1] with +the option '--lost-found' instead. + Finds dangling commits and tags from the object database, and creates refs to them in the .git/lost-found/ directory. Commits and tags that dereference to commits are stored in .git/lost-found/commit, diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 9e454f0a4d..2ec0c0d270 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -15,6 +15,7 @@ SYNOPSIS [-x <pattern>|--exclude=<pattern>] [-X <file>|--exclude-from=<file>] [--exclude-per-directory=<file>] + [--exclude-standard] [--error-unmatch] [--with-tree=<tree-ish>] [--full-name] [--abbrev] [--] [<file>]\* @@ -77,6 +78,10 @@ OPTIONS read additional exclude patterns that apply only to the directory and its subdirectories in <file>. +--exclude-standard:: + Add the standard git exclusions: .git/info/exclude, .gitignore + in each directory, and the user's global exclusion file. + --error-unmatch:: If any <file> does not appear in the index, treat this as an error (return 1). diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 17e9f10c65..b726ddfe12 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,7 +40,7 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling git's imitation of +Typically this is run with a script calling git's imitation of the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 827838f7d0..eabd7ef33f 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -59,6 +59,10 @@ merge.verbosity:: above outputs debugging information. The default is level 2. Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. +branch.<name>.mergeoptions:: + Sets default options for merging into branch <name>. The syntax and + supported options are equal to that of git-merge, but option values + containing whitespace characters are currently not supported. HOW MERGE WORKS --------------- diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 6c32c6d18e..a26c260162 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -25,12 +25,18 @@ OPTIONS -t or --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff + kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff + If a merge resolution program is not specified, 'git mergetool' will use the configuration variable merge.tool. If the configuration variable merge.tool is not set, 'git mergetool' will pick a suitable default. ++ +You can explicitly provide a full path to the tool by setting the +configuration variable mergetool.<tool>.path. For example, you +can configure the absolute path to kdiff3 by setting +mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool +is available in PATH. Author ------ diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index 2c9cf743c7..3b8ca76dff 100644 --- a/Documentation/git-mv.txt +++ b/Documentation/git-mv.txt @@ -34,7 +34,7 @@ OPTIONS condition. An error happens when a source is neither existing nor controlled by GIT, or when it would overwrite an existing file unless '-f' is given. --n:: +-n, \--dry-run:: Do nothing; only show what would happen diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index d18259d93f..5237ab0c04 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -169,6 +169,14 @@ base-name:: length, this option typically shrinks the resulting packfile by 3-5 per-cent. +--threads=<n>:: + Specifies the number of threads to spawn when searching for best + delta matches. This requires that pack-objects be compiled with + pthreads otherwise this option is ignored with a warning. + This is meant to reduce packing time on multiprocessor machines. + The required amount of memory for the delta search window is + however multiplied by the number of threads. + --index-version=<version>[,<offset>]:: This is intended to be used by the test suite only. It allows to force the version for the generated pack index, and to force diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt index abc171266a..38a5325af7 100644 --- a/Documentation/git-peek-remote.txt +++ b/Documentation/git-peek-remote.txt @@ -12,8 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -Lists the references the remote repository has, and optionally -stores them in the local repository under the same name. +This command is deprecated; use `git-ls-remote` instead. OPTIONS ------- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 6bc559ddd8..b8003c63c7 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,8 +9,8 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git-push' [--all] [--tags] [--receive-pack=<git-receive-pack>] - [--repo=all] [-f | --force] [-v] [<repository> <refspec>...] +'git-push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] + [--repo=all] [-f | --force] [-v | --verbose] [<repository> <refspec>...] DESCRIPTION ----------- @@ -63,6 +63,17 @@ the remote repository. Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/heads/` be pushed. +\--mirror:: + Instead of naming each ref to push, specifies that all + refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/` + be mirrored to the remote repository. Newly created local + refs will be pushed to the remote end, locally updated refs + will be force updated on the remote end, and deleted refs + will be removed from the remote end. + +\--dry-run:: + Do everything except actually send the updates. + \--tags:: All refs under `$GIT_DIR/refs/tags` are pushed, in addition to refspecs explicitly listed on the command @@ -92,7 +103,7 @@ the remote repository. transfer spends extra cycles to minimize the number of objects to be sent and meant to be used on slower connection. --v:: +-v, \--verbose:: Run verbosely. include::urls-remotes.txt[] diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index dfb8a0da5b..e4326d3322 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-C<n>] - [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>] +'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] + [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] + [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort DESCRIPTION @@ -27,7 +28,10 @@ The current branch is reset to <upstream>, or <newbase> if the `git reset --hard <upstream>` (or <newbase>). The commits that were previously saved into the temporary area are -then reapplied to the current branch, one by one, in order. +then reapplied to the current branch, one by one, in order. Note that +any commits in HEAD which introduce the same textual changes as a commit +in HEAD..<upstream> are omitted (i.e., a patch already accepted upstream +with a different commit message or timestamp will be skipped). It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure @@ -61,6 +65,26 @@ would be: The latter form is just a short-hand of `git checkout topic` followed by `git rebase master`. +If the upstream branch already contains a change you have made (e.g., +because you mailed a patch which was applied upstream), then that commit +will be skipped. For example, running `git-rebase master` on the +following history (in which A' and A introduce the same set of changes, +but have different committer information): + +------------ + A---B---C topic + / + D---E---A'---F master +------------ + +will result in: + +------------ + B'---C' topic + / + D---E---A'---F master +------------ + Here is how you would transplant a topic branch based on one branch to another, to pretend that you forked the topic branch from the latter branch, using `rebase --onto`. @@ -209,6 +233,10 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +--whitespace=<nowarn|warn|error|error-all|strip>:: + This flag is passed to the `git-apply` program + (see gitlink:git-apply[1]) that applies the patch. + -i, \--interactive:: Make a list of the commits which are about to be rebased. Let the user edit that list before rebasing. This mode can also be used to diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 886bc03c4a..4b263c249c 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git-remote' -'git-remote' add [-t <branch>] [-m <branch>] [-f] <name> <url> +'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url> +'git-remote' rm <name> 'git-remote' show <name> 'git-remote' prune <name> 'git-remote' update [group] @@ -45,6 +46,15 @@ multiple branches without grabbing all branches. With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set up to point at remote's `<master>` branch instead of whatever branch the `HEAD` at the remote repository actually points at. ++ +In mirror mode, enabled with `--mirror`, the refs will not be stored +in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option +only makes sense in bare repositories. + +'rm':: + +Remove the remote named <name>. All remote tracking branches and +configuration settings for the remote are removed. 'show':: diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 87afa6f8da..050e4eadbb 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git-reset' [--mixed | --soft | --hard] [<commit>] -'git-reset' [--mixed] <commit> [--] <paths>... +'git-reset' [--mixed | --soft | --hard] [-q] [<commit>] +'git-reset' [--mixed] [-q] <commit> [--] <paths>... DESCRIPTION ----------- @@ -45,6 +45,9 @@ OPTIONS switched to. Any changes to tracked files in the working tree since <commit> are lost. +-q:: + Be quiet, only report errors. + <commit>:: Commit to make the current HEAD. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 7cd0e8913e..989fbf3562 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -20,6 +20,7 @@ SYNOPSIS [ \--not ] [ \--all ] [ \--stdin ] + [ \--quiet ] [ \--topo-order ] [ \--parents ] [ \--timestamp ] @@ -34,6 +35,7 @@ SYNOPSIS [ \--pretty | \--header ] [ \--bisect ] [ \--bisect-vars ] + [ \--bisect-all ] [ \--merge ] [ \--reverse ] [ \--walk-reflogs ] @@ -269,6 +271,14 @@ limiting may be applied. In addition to the '<commit>' listed on the command line, read them from the standard input. +--quiet:: + + Don't print anything to standard output. This form of + git-rev-list is primarly meant to allow the caller to + test the exit status to see if a range of objects is fully + connected (or not). It is faster than redirecting stdout + to /dev/null as the output does not have to be formatted. + --cherry-pick:: Omit any commit that introduces the same change as @@ -354,6 +364,21 @@ the expected number of commits to be tested if `bisect_rev` turns out to be bad to `bisect_bad`, and the number of commits we are bisecting right now to `bisect_all`. +--bisect-all:: + +This outputs all the commit objects between the included and excluded +commits, ordered by their distance to the included and excluded +commits. The farthest from them is displayed first. (This is the only +one displayed by `--bisect`.) + +This is useful because it makes it easy to choose a good commit to +test when you want to avoid to test some of them for some reason (they +may not compile for example). + +This option can be used along with `--bisect-vars`, in this case, +after all the sorted commit objects, there will be the same text as if +`--bisect-vars` had been used alone. + -- Commit Ordering diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 4758c33dee..329fce0aab 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -23,6 +23,13 @@ distinguish between them. OPTIONS ------- +--parseopt:: + Use `git-rev-parse` in option parsing mode (see PARSEOPT section below). + +--keep-dash-dash:: + Only meaningful in `--parseopt` mode. Tells the option parser to echo + out the first `--` met instead of skipping it. + --revs-only:: Do not output flags and parameters not meant for `git-rev-list` command. @@ -288,10 +295,75 @@ Here are a handful examples: C^@ I J F F^! D G H D F +PARSEOPT +-------- + +In `--parseopt` mode, `git-rev-parse` helps massaging options to bring to shell +scripts the same facilities C builtins have. It works as an option normalizer +(e.g. splits single switches aggregate values), a bit like `getopt(1)` does. + +It takes on the standard input the specification of the options to parse and +understand, and echoes on the standard output a line suitable for `sh(1)` `eval` +to replace the arguments with normalized ones. In case of error, it outputs +usage on the standard error stream, and exits with code 129. + +Input Format +~~~~~~~~~~~~ + +`git-rev-parse --parseopt` input format is fully text based. It has two parts, +separated by a line that contains only `--`. The lines before the separator +(should be more than one) are used for the usage. +The lines after the separator describe the options. + +Each line of options has this format: + +------------ +<opt_spec><arg_spec>? SP+ help LF +------------ + +`<opt_spec>`:: + its format is the short option character, then the long option name + separated by a comma. Both parts are not required, though at least one + is necessary. `h,help`, `dry-run` and `f` are all three correct + `<opt_spec>`. + +`<arg_spec>`:: + an `<arg_spec>` tells the option parser if the option has an argument + (`=`), an optional one (`?` though its use is discouraged) or none + (no `<arg_spec>` in that case). + +The remainder of the line, after stripping the spaces, is used +as the help associated to the option. + +Blank lines are ignored, and lines that don't match this specification are used +as option group headers (start the line with a space to create such +lines on purpose). + +Example +~~~~~~~ + +------------ +OPTS_SPEC="\ +some-command [options] <args>... + +some-command does foo and bar! +-- +h,help show the help + +foo some nifty option --foo +bar= some cool option --bar with an argument + + An option group Header +C? option C with an optional argument" + +eval `echo "$OPTS_SPEC" | git-rev-parse --parseopt -- "$@" || echo exit $?` +------------ + + Author ------ -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <junkio@cox.net> +Written by Linus Torvalds <torvalds@osdl.org> . +Junio C Hamano <junkio@cox.net> and Pierre Habouzit <madcoder@debian.org> Documentation -------------- diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 69db498447..3457c40787 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -7,7 +7,7 @@ git-revert - Revert an existing commit SYNOPSIS -------- -'git-revert' [--edit | --no-edit] [-n] <commit> +'git-revert' [--edit | --no-edit] [-n] [-m parent-number] <commit> DESCRIPTION ----------- @@ -27,6 +27,13 @@ OPTIONS message prior committing the revert. This is the default if you run the command from a terminal. +-m parent-number|--mainline parent-number:: + Usually you cannot revert a merge because you do not know which + side of the merge should be considered the mainline. This + option specifies the parent number (starting from 1) of + the mainline and allows revert to reverse the change + relative to the specified parent. + --no-edit:: With this option, `git-revert` will not start the commit message editor. diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index be61a82164..48c1d97f93 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -30,7 +30,7 @@ OPTIONS -f:: Override the up-to-date check. --n:: +-n, \--dry-run:: Don't actually remove the file(s), just show if they exist in the index. @@ -51,7 +51,7 @@ OPTIONS \--ignore-unmatch:: Exit with a zero status even if no files matched. -\--quiet:: +-q, \--quiet:: git-rm normally outputs one line (in the form of an "rm" command) for each file removed. This option suppresses that output. diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 16bfd7be22..659215ac72 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -75,6 +75,12 @@ The --cc option must be repeated for each user you want on the cc list. Make git-send-email less verbose. One line per email should be all that is output. +--identity:: + A configuration identity. When given, causes values in the + 'sendemail.<identity>' subsection to take precedence over + values in the 'sendemail' section. The default identity is + the value of 'sendemail.identity'. + --smtp-server:: If set, specifies the outgoing SMTP server to use (e.g. `smtp.example.com` or a raw IP address). Alternatively it can @@ -85,14 +91,29 @@ The --cc option must be repeated for each user you want on the cc list. `/usr/lib/sendmail` if such program is available, or `localhost` otherwise. +--smtp-server-port:: + Specifies a port different from the default port (SMTP + servers typically listen to smtp port 25 and ssmtp port + 465). + +--smtp-user, --smtp-pass:: + Username and password for SMTP-AUTH. Defaults are the values of + the configuration values 'sendemail.smtpuser' and + 'sendemail.smtppass', but see also 'sendemail.identity'. + If not set, authentication is not attempted. + +--smtp-ssl:: + If set, connects to the SMTP server using SSL. + Default is the value of the 'sendemail.smtpssl' configuration value; + if that is unspecified, does not use SSL. + --subject:: Specify the initial subject of the email thread. Only necessary if --compose is also set. If --compose is not set, this will be prompted for. --suppress-from, --no-suppress-from:: - If this is set, do not add the From: address to the cc: list, if it - shows up in a From: line. + If this is set, do not add the From: address to the cc: list. Default is the value of 'sendemail.suppressfrom' configuration value; if that is unspecified, default to --no-suppress-from. @@ -122,6 +143,13 @@ The --to option must be repeated for each user you want on the to list. CONFIGURATION ------------- +sendemail.identity:: + The default configuration identity. When specified, + 'sendemail.<identity>.<item>' will have higher precedence than + 'sendemail.<item>'. This is useful to declare multiple SMTP + identities and to hoist sensitive authentication information + out of the repository and into the global configuation file. + sendemail.aliasesfile:: To avoid typing long email addresses, point this to one or more email aliases files. You must also supply 'sendemail.aliasfiletype'. @@ -130,6 +158,9 @@ sendemail.aliasfiletype:: Format of the file(s) specified in sendemail.aliasesfile. Must be one of 'mutt', 'mailrc', 'pine', or 'gnus'. +sendemail.to:: + Email address (or alias) to always send to. + sendemail.cccmd:: Command to execute to generate per patch file specific "Cc:"s. @@ -141,7 +172,16 @@ sendemail.chainreplyto:: parameter. sendemail.smtpserver:: - Default smtp server to use. + Default SMTP server to use. + +sendemail.smtpuser:: + Default SMTP-AUTH username. + +sendemail.smtppass:: + Default SMTP-AUTH password. + +sendemail.smtpssl:: + Boolean value specifying the default to the '--smtp-ssl' parameter. Author ------ diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 3271e88183..2fa01d4a3c 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -8,7 +8,7 @@ git-send-pack - Push objects over git protocol to another repository SYNOPSIS -------- -'git-send-pack' [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...] +'git-send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...] DESCRIPTION ----------- @@ -34,6 +34,9 @@ OPTIONS Instead of explicitly specifying which refs to update, update all heads that locally exist. +\--dry-run:: + Do everything except actually send the updates. + \--force:: Usually, the command refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. diff --git a/Documentation/git-ssh-fetch.txt b/Documentation/git-ssh-fetch.txt deleted file mode 100644 index 8d3e2ffb2c..0000000000 --- a/Documentation/git-ssh-fetch.txt +++ /dev/null @@ -1,52 +0,0 @@ -git-ssh-fetch(1) -================ - -NAME ----- -git-ssh-fetch - Fetch from a remote repository over ssh connection - - - -SYNOPSIS --------- -'git-ssh-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url - -DESCRIPTION ------------ -THIS COMMAND IS DEPRECATED. - -Pulls from a remote repository over ssh connection, invoking -git-ssh-upload on the other end. It functions identically to -git-ssh-upload, aside from which end you run it on. - - -OPTIONS -------- -commit-id:: - Either the hash or the filename under [URL]/refs/ to - pull. - --c:: - Get the commit objects. --t:: - Get trees associated with the commit objects. --a:: - Get all the objects. --v:: - Report what is downloaded. --w:: - Writes the commit-id into the filename under $GIT_DIR/refs/ on - the local end after the transfer is complete. - - -Author ------- -Written by Daniel Barkalow <barkalow@iabervon.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git-ssh-upload.txt b/Documentation/git-ssh-upload.txt deleted file mode 100644 index 5e2ca8dccf..0000000000 --- a/Documentation/git-ssh-upload.txt +++ /dev/null @@ -1,48 +0,0 @@ -git-ssh-upload(1) -================= - -NAME ----- -git-ssh-upload - Push to a remote repository over ssh connection - - -SYNOPSIS --------- -'git-ssh-upload' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url - -DESCRIPTION ------------ -THIS COMMAND IS DEPRECATED. - -Pushes from a remote repository over ssh connection, invoking -git-ssh-fetch on the other end. It functions identically to -git-ssh-fetch, aside from which end you run it on. - -OPTIONS -------- -commit-id:: - Id of commit to push. - --c:: - Get the commit objects. --t:: - Get tree associated with the requested commit object. --a:: - Get all the objects. --v:: - Report what is uploaded. --w:: - Writes the commit-id into the filename under [URL]/refs/ on - the remote end after the transfer is complete. - -Author ------- -Written by Daniel Barkalow <barkalow@iabervon.org> - -Documentation --------------- -Documentation by Daniel Barkalow - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 5723bb06f0..c0147b99a2 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -57,7 +57,7 @@ stash@{1}: On master: 9cc0589... Add git-stash show [<stash>]:: - Show the changes recorded in the stash as a diff between the the + Show the changes recorded in the stash as a diff between the stashed state and its original parent. When no `<stash>` is given, shows the latest one. By default, the command shows the diffstat, but it will accept any format known to `git-diff` (e.g., `git-stash show diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 2c48936fcd..335e973a6a 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -21,6 +21,9 @@ add:: repository is cloned at the specified path, added to the changeset and registered in .gitmodules. If no path is specified, the path is deduced from the repository specification. + If the repository url begins with ./ or ../, it is stored as + given but resolved as a relative path from the main project's + url when cloning. status:: Show the status of the submodules. This will print the SHA-1 of the diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index e157c6ab50..918a9928b1 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -193,6 +193,12 @@ Any other arguments are passed directly to `git log' repository (that has been init-ed with git-svn). The -r<revision> option is required for this. +'info':: + Shows information about a file or directory similar to what + `svn info' provides. Does not currently support a -r/--revision + argument. Use the --url option to output only the value of the + 'URL:' field. + -- OPTIONS @@ -404,7 +410,7 @@ section because they affect the 'git-svn-id:' metadata line. BASIC EXAMPLES -------------- -Tracking and contributing to a the trunk of a Subversion-managed project: +Tracking and contributing to the trunk of a Subversion-managed project: ------------------------------------------------------------------------ # Clone a repo (like git clone): diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index a88f722860..694cabab24 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -26,7 +26,7 @@ a regular file whose contents is `ref: refs/heads/master`. OPTIONS ------- --q:: +-q, --quiet:: Do not issue an error message if the <name> is not a symbolic ref but a detached HEAD; instead exit with non-zero status silently. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 990ae4f948..10d3e3fa95 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -112,7 +112,7 @@ You really want to call the new version "X" too, 'even though' others have already seen the old one. So just use "git tag -f" again, as if you hadn't already published the old one. -However, Git does *not* (and it should not)change tags behind +However, Git does *not* (and it should not) change tags behind users back. So if somebody already got the old tag, doing a "git pull" on your tree shouldn't just make them overwrite the old one. @@ -214,6 +214,27 @@ having tracking branches. Again, the heuristic to automatically follow such tags is a good thing. +On Backdating Tags +~~~~~~~~~~~~~~~~~~ + +If you have imported some changes from another VCS and would like +to add tags for major releases of your work, it is useful to be able +to specify the date to embed inside of the tag object. The data in +the tag object affects, for example, the ordering of tags in the +gitweb interface. + +To set the date used in future tag objects, set the environment +variable GIT_AUTHOR_DATE to one or more of the date and time. The +date and time can be specified in a number of ways; the most common +is "YYYY-MM-DD HH:MM". + +An example follows. + +------------ +$ GIT_AUTHOR_DATE="2006-10-02 10:31" git tag -s v1.0.1 +------------ + + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 10653ff898..a96403cb8c 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -22,6 +22,9 @@ Alternative/Augmentative Porcelains providing generally smoother user experience than the "raw" Core GIT itself and indeed many other version control systems. + Cogito is no longer maintained as most of its functionality + is now in core GIT. + - *pg* (http://www.spearce.org/category/projects/scm/pg/) @@ -33,7 +36,7 @@ Alternative/Augmentative Porcelains - *StGit* (http://www.procode.org/stgit/) Stacked GIT provides a quilt-like patch management functionality in the - GIT environment. You can easily manage your patches in the scope of GIT + GIT environment. You can easily manage your patches in the scope of GIT until they get merged upstream. diff --git a/Documentation/git.txt b/Documentation/git.txt index a7cd91acc1..546020100a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,7 +46,13 @@ Documentation for older releases are available here: * link:v1.5.3/git.html[documentation for release 1.5.3] * release notes for - link:RelNotes-1.5.3.1.txt[1.5.3.1]. + link:RelNotes-1.5.3.6.txt[1.5.3.6], + link:RelNotes-1.5.3.5.txt[1.5.3.5], + link:RelNotes-1.5.3.4.txt[1.5.3.4], + link:RelNotes-1.5.3.3.txt[1.5.3.3], + link:RelNotes-1.5.3.2.txt[1.5.3.2], + link:RelNotes-1.5.3.1.txt[1.5.3.1], + link:RelNotes-1.5.3.txt[1.5.3]. * release notes for link:RelNotes-1.5.2.5.txt[1.5.2.5], @@ -323,7 +329,7 @@ For a more complete list of ways to spell object names, see File/Directory Structure ------------------------ -Please see link:repository-layout.html[repository layout] document. +Please see the link:repository-layout.html[repository layout] document. Read link:hooks.html[hooks] for more details about each hook. @@ -333,7 +339,7 @@ Higher level SCMs may provide and manage additional information in the Terminology ----------- -Please see link:glossary.html[glossary] document. +Please see the link:glossary.html[glossary] document. Environment Variables diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index cf4ee2ebe5..19bd25f299 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -410,6 +410,23 @@ frotz unspecified ---------------------------------------------------------------- +Creating an archive +~~~~~~~~~~~~~~~~~~~ + +`export-subst` +^^^^^^^^^^^^^^ + +If the attribute `export-subst` is set for a file then git will expand +several placeholders when adding this file to an archive. The +expansion depends on the availability of a commit ID, i.e. if +gitlink:git-archive[1] has been given a tree instead of a commit or a +tag then no replacement will be done. The placeholders are the same +as those for the option `--pretty=format:` of gitlink:git-log[1], +except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` +in the file. E.g. the string `$Format:%H$` will be replaced by the +commit hash. + + GIT --- Part of the gitlink:git[7] suite diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index 3f7b1e42b5..fc1874424e 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -52,8 +52,8 @@ GIT Glossary [[def_cherry-picking]]cherry-picking:: In <<def_SCM,SCM>> jargon, "cherry pick" means to choose a subset of changes out of a series of changes (typically commits) and record them - as a new series of changes on top of different codebase. In GIT, this is - performed by "git cherry-pick" command to extract the change introduced + as a new series of changes on top of a different codebase. In GIT, this is + performed by the "git cherry-pick" command to extract the change introduced by an existing <<def_commit,commit>> and to record it based on the tip of the current <<def_branch,branch>> as a new commit. @@ -281,7 +281,7 @@ This commit is referred to as a "merge commit", or sometimes just a [[def_pickaxe]]pickaxe:: The term <<def_pickaxe,pickaxe>> refers to an option to the diffcore routines that help select changes that add or delete a given text - string. With the --pickaxe-all option, it can be used to view the full + string. With the `--pickaxe-all` option, it can be used to view the full <<def_changeset,changeset>> that introduced or removed, say, a particular line of text. See gitlink:git-diff[1]. @@ -301,8 +301,8 @@ This commit is referred to as a "merge commit", or sometimes just a [[def_push]]push:: Pushing a <<def_branch,branch>> means to get the branch's <<def_head_ref,head ref>> from a remote <<def_repository,repository>>, - find out if it is an ancestor to the branch's local - head ref is a direct, and in that case, putting all + find out if it is a direct ancestor to the branch's local + head ref, and in that case, putting all objects, which are <<def_reachable,reachable>> from the local head ref, and which are missing from the remote repository, into the remote @@ -347,7 +347,7 @@ This commit is referred to as a "merge commit", or sometimes just a it as my origin branch head". And `git push $URL refs/heads/master:refs/heads/to-upstream` means "publish my master branch head as to-upstream branch at $URL". See also - gitlink:git-push[1] + gitlink:git-push[1]. [[def_repository]]repository:: A collection of <<def_ref,refs>> together with an diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index c39edc57c4..f110162b01 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -87,6 +87,33 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. +post-checkout +----------- + +This hook is invoked when a `git-checkout` is run after having updated the +worktree. The hook is given three parameters: the ref of the previous HEAD, +the ref of the new HEAD (which may or may not have changed), and a flag +indicating whether the checkout was a branch checkout (changing branches, +flag=1) or a file checkout (retrieving a file from the index, flag=0). +This hook cannot affect the outcome of `git-checkout`. + +This hook can be used to perform repository validity checks, auto-display +differences from the previous HEAD if different, or set working dir metadata +properties. + +post-merge +----------- + +This hook is invoked by `git-merge`, which happens when a `git pull` +is done on a local repository. The hook takes a single parameter, a status +flag specifying whether or not the merge being done was a squash merge. +This hook cannot affect the outcome of `git-merge`. + +This hook can be used in conjunction with a corresponding pre-commit hook to +save and restore any form of metadata associated with the working tree +(eg: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl +for an example of how to do this. + [[pre-receive]] pre-receive ----------- diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt new file mode 100644 index 0000000000..4357e26913 --- /dev/null +++ b/Documentation/howto/maintain-git.txt @@ -0,0 +1,277 @@ +From: Junio C Hamano <gitster@pobox.com> +Date: Wed, 21 Nov 2007 16:32:55 -0800 +Subject: Addendum to "MaintNotes" +Abstract: Imagine that git development is racing along as usual, when our friendly + neighborhood maintainer is struck down by a wayward bus. Out of the + hordes of suckers (loyal developers), you have been tricked (chosen) to + step up as the new maintainer. This howto will show you "how to" do it. + +The maintainer's git time is spent on three activities. + + - Communication (60%) + + Mailing list discussions on general design, fielding user + questions, diagnosing bug reports; reviewing, commenting on, + suggesting alternatives to, and rejecting patches. + + - Integration (30%) + + Applying new patches from the contributors while spotting and + correcting minor mistakes, shuffling the integration and + testing branches, pushing the results out, cutting the + releases, and making announcements. + + - Own development (10%) + + Scratching my own itch and sending proposed patch series out. + +The policy on Integration is informally mentioned in "A Note +from the maintainer" message, which is periodically posted to +this mailing list after each feature release is made. + +The policy. + + - Feature releases are numbered as vX.Y.Z and are meant to + contain bugfixes and enhancements in any area, including + functionality, performance and usability, without regression. + + - Maintenance releases are numbered as vX.Y.Z.W and are meant + to contain only bugfixes for the corresponding vX.Y.Z feature + release and earlier maintenance releases vX.Y.Z.V (V < W). + + - 'master' branch is used to prepare for the next feature + release. In other words, at some point, the tip of 'master' + branch is tagged with vX.Y.Z. + + - 'maint' branch is used to prepare for the next maintenance + release. After the feature release vX.Y.Z is made, the tip + of 'maint' branch is set to that release, and bugfixes will + accumulate on the branch, and at some point, the tip of the + branch is tagged with vX.Y.Z.1, vX.Y.Z.2, and so on. + + - 'next' branch is used to publish changes (both enhancements + and fixes) that (1) have worthwhile goal, (2) are in a fairly + good shape suitable for everyday use, (3) but have not yet + demonstrated to be regression free. New changes are tested + in 'next' before merged to 'master'. + + - 'pu' branch is used to publish other proposed changes that do + not yet pass the criteria set for 'next'. + + - The tips of 'master', 'maint' and 'next' branches will always + fast forward, to allow people to build their own + customization on top of them. + + - Usually 'master' contains all of 'maint', 'next' contains all + of 'master' and 'pu' contains all of 'next'. + + - The tip of 'master' is meant to be more stable than any + tagged releases, and the users are encouraged to follow it. + + - The 'next' branch is where new action takes place, and the + users are encouraged to test it so that regressions and bugs + are found before new topics are merged to 'master'. + + +A typical git day for the maintainer implements the above policy +by doing the following: + + - Scan mailing list and #git channel log. Respond with review + comments, suggestions etc. Kibitz. Collect potentially + usable patches from the mailing list. Patches about a single + topic go to one mailbox (I read my mail in Gnus, and type + \C-o to save/append messages in files in mbox format). + + - Review the patches in the saved mailboxes. Edit proposed log + message for typofixes and clarifications, and add Acks + collected from the list. Edit patch to incorporate "Oops, + that should have been like this" fixes from the discussion. + + - Classify the collected patches and handle 'master' and + 'maint' updates: + + - Obviously correct fixes that pertain to the tip of 'maint' + are directly applied to 'maint'. + + - Obviously correct fixes that pertain to the tip of 'master' + are directly applied to 'master'. + + This step is done with "git am". + + $ git checkout master ;# or "git checkout maint" + $ git am -3 -s mailbox + $ make test + + - Merge downwards (maint->master): + + $ git checkout master + $ git merge maint + $ make test + + - Review the last issue of "What's cooking" message, review the + topics scheduled for merging upwards (topic->master and + topic->maint), and merge. + + $ git checkout master ;# or "git checkout maint" + $ git merge ai/topic ;# or "git merge ai/maint-topic" + $ git log -p ORIG_HEAD.. ;# final review + $ git diff ORIG_HEAD.. ;# final review + $ make test ;# final review + $ git branch -d ai/topic ;# or "git branch -d ai/maint-topic" + + - Merge downwards (maint->master) if needed: + + $ git checkout master + $ git merge maint + $ make test + + - Merge downwards (master->next) if needed: + + $ git checkout next + $ git merge master + $ make test + + - Handle the remaining patches: + + - Anything unobvious that is applicable to 'master' (in other + words, does not depend on anything that is still in 'next' + and not in 'master') is applied to a new topic branch that + is forked from the tip of 'master'. This includes both + enhancements and unobvious fixes to 'master'. A topic + branch is named as ai/topic where "ai" is typically + author's initial and "topic" is a descriptive name of the + topic (in other words, "what's the series is about"). + + - An unobvious fix meant for 'maint' is applied to a new + topic branch that is forked from the tip of 'maint'. The + topic is named as ai/maint-topic. + + - Changes that pertain to an existing topic are applied to + the branch, but: + + - obviously correct ones are applied first; + + - questionable ones are discarded or applied to near the tip; + + - Replacement patches to an existing topic are accepted only + for commits not in 'next'. + + The above except the "replacement" are all done with: + + $ git am -3 -s mailbox + + while patch replacement is often done by: + + $ git format-patch ai/topic~$n..ai/topic ;# export existing + + then replace some parts with the new patch, and reapplying: + + $ git reset --hard ai/topic~$n + $ git am -3 -s 000*.txt + + The full test suite is always run for 'maint' and 'master' + after patch application; for topic branches the tests are run + as time permits. + + - Update "What's cooking" message to review the updates to + existing topics, newly added topics and graduated topics. + + This step is helped with Meta/UWC script (where Meta/ contains + a checkout of the 'todo' branch). + + - Merge topics to 'next'. For each branch whose tip is not + merged to 'next', one of three things can happen: + + - The commits are all next-worthy; merge the topic to next: + + $ git checkout next + $ git merge ai/topic ;# or "git merge ai/maint-topic" + $ make test + + - The new parts are of mixed quality, but earlier ones are + next-worthy; merge the early parts to next: + + $ git checkout next + $ git merge ai/topic~2 ;# the tip two are dubious + $ make test + + - Nothing is next-worthy; do not do anything. + + - Rebase topics that do not have any commit in next yet. This + step is optional but sometimes is worth doing when an old + series that is not in next can take advantage of low-level + framework change that is merged to 'master' already. + + $ git rebase master ai/topic + + This step is helped with Meta/git-topic.perl script to + identify which topic is rebaseable. There also is a + pre-rebase hook to make sure that topics that are already in + 'next' are not rebased beyond the merged commit. + + - Rebuild "pu" to merge the tips of topics not in 'next'. + + $ git checkout pu + $ git reset --hard next + $ git merge ai/topic ;# repeat for all remaining topics + $ make test + + This step is helped with Meta/PU script + + - Push four integration branches to a private repository at + k.org and run "make test" on all of them. + + - Push four integration branches to /pub/scm/git/git.git at + k.org. This triggers its post-update hook which: + + (1) runs "git pull" in $HOME/git-doc/ repository to pull + 'master' just pushed out; + + (2) runs "make doc" in $HOME/git-doc/, install the generated + documentation in staging areas, which are separate + repositories that have html and man branches checked + out. + + (3) runs "git commit" in the staging areas, and run "git + push" back to /pub/scm/git/git.git/ to update the html + and man branches. + + (4) installs generated documentation to /pub/software/scm/git/docs/ + to be viewed from http://www.kernel.org/ + + - Fetch html and man branches back from k.org, and push four + integration branches and the two documentation branches to + repo.or.cz + + +Some observations to be made. + + * Each topic is tested individually, and also together with + other topics cooking in 'next'. Until it matures, none part + of it is merged to 'master'. + + * A topic already in 'next' can get fixes while still in + 'next'. Such a topic will have many merges to 'next' (in + other words, "git log --first-parent next" will show many + "Merge ai/topic to next" for the same topic. + + * An unobvious fix for 'maint' is cooked in 'next' and then + merged to 'master' to make extra sure it is Ok and then + merged to 'maint'. + + * Even when 'next' becomes empty (in other words, all topics + prove stable and are merged to 'master' and "git diff master + next" shows empty), it has tons of merge commits that will + never be in 'master'. + + * In principle, "git log --first-parent master..next" should + show nothing but merges (in practice, there are fixup commits + and reverts that are not merges). + + * Commits near the tip of a topic branch that are not in 'next' + are fair game to be discarded, replaced or rewritten. + Commits already merged to 'next' will not be. + + * Being in the 'next' branch is not a guarantee for a topic to + be included in the next feature release. Being in the + 'master' branch typically is. diff --git a/Documentation/howto/recover-corrupted-blob-object.txt b/Documentation/howto/recover-corrupted-blob-object.txt new file mode 100644 index 0000000000..323b513ed0 --- /dev/null +++ b/Documentation/howto/recover-corrupted-blob-object.txt @@ -0,0 +1,134 @@ +Date: Fri, 9 Nov 2007 08:28:38 -0800 (PST) +From: Linus Torvalds <torvalds@linux-foundation.org> +Subject: corrupt object on git-gc +Abstract: Some tricks to reconstruct blob objects in order to fix + a corrupted repository. + +On Fri, 9 Nov 2007, Yossi Leybovich wrote: +> +> Did not help still the repository look for this object? +> Any one know how can I track this object and understand which file is it + +So exactly *because* the SHA1 hash is cryptographically secure, the hash +itself doesn't actually tell you anything, in order to fix a corrupt +object you basically have to find the "original source" for it. + +The easiest way to do that is almost always to have backups, and find the +same object somewhere else. Backups really are a good idea, and git makes +it pretty easy (if nothing else, just clone the repository somewhere else, +and make sure that you do *not* use a hard-linked clone, and preferably +not the same disk/machine). + +But since you don't seem to have backups right now, the good news is that +especially with a single blob being corrupt, these things *are* somewhat +debuggable. + +First off, move the corrupt object away, and *save* it. The most common +cause of corruption so far has been memory corruption, but even so, there +are people who would be interested in seeing the corruption - but it's +basically impossible to judge the corruption until we can also see the +original object, so right now the corrupt object is useless, but it's very +interesting for the future, in the hope that you can re-create a +non-corrupt version. + +So: + +> ib]$ mv .git/objects/4b/9458b3786228369c63936db65827de3cc06200 ../ + +This is the right thing to do, although it's usually best to save it under +it's full SHA1 name (you just dropped the "4b" from the result ;). + +Let's see what that tells us: + +> ib]$ git-fsck --full +> broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 +> to blob 4b9458b3786228369c63936db65827de3cc06200 +> missing blob 4b9458b3786228369c63936db65827de3cc06200 + +Ok, I removed the "dangling commit" messages, because they are just +messages about the fact that you probably have rebased etc, so they're not +at all interesting. But what remains is still very useful. In particular, +we now know which tree points to it! + +Now you can do + + git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 + +which will show something like + + 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore + 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap + 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING + 100644 blob ee909f2cc49e54f0799a4739d24c4cb9151ae453 CREDITS + 040000 tree 0f5f709c17ad89e72bdbbef6ea221c69807009f6 Documentation + 100644 blob 1570d248ad9237e4fa6e4d079336b9da62d9ba32 Kbuild + 100644 blob 1c7c229a092665b11cd46a25dbd40feeb31661d9 MAINTAINERS + ... + +and you should now have a line that looks like + + 10064 blob 4b9458b3786228369c63936db65827de3cc06200 my-magic-file + +in the output. This already tells you a *lot* it tells you what file the +corrupt blob came from! + +Now, it doesn't tell you quite enough, though: it doesn't tell what +*version* of the file didn't get correctly written! You might be really +lucky, and it may be the version that you already have checked out in your +working tree, in which case fixing this problem is really simple, just do + + git hash-object -w my-magic-file + +again, and if it outputs the missing SHA1 (4b945..) you're now all done! + +But that's the really lucky case, so let's assume that it was some older +version that was broken. How do you tell which version it was? + +The easiest way to do it is to do + + git log --raw --all --full-history -- subdirectory/my-magic-file + +and that will show you the whole log for that file (please realize that +the tree you had may not be the top-level tree, so you need to figure out +which subdirectory it was in on your own), and because you're asking for +raw output, you'll now get something like + + commit abc + Author: + Date: + .. + :100644 100644 4b9458b... newsha... M somedirectory/my-magic-file + + + commit xyz + Author: + Date: + + .. + :100644 100644 oldsha... 4b9458b... M somedirectory/my-magic-file + +and this actually tells you what the *previous* and *subsequent* versions +of that file were! So now you can look at those ("oldsha" and "newsha" +respectively), and hopefully you have done commits often, and can +re-create the missing my-magic-file version by looking at those older and +newer versions! + +If you can do that, you can now recreate the missing object with + + git hash-object -w <recreated-file> + +and your repository is good again! + +(Btw, you could have ignored the fsck, and started with doing a + + git log --raw --all + +and just looked for the sha of the missing object (4b9458b..) in that +whole thing. It's up to you - git does *have* a lot of information, it is +just missing one particular blob version. + +Trying to recreate trees and especially commits is *much* harder. So you +were lucky that it's a blob. It's quite possible that you can recreate the +thing. + + Linus diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index d64c259bb3..9f1fc82550 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -10,6 +10,10 @@ not autocommit, to give the user a chance to inspect and further tweak the merge result before committing. +--commit:: + Perform the merge and commit the result. This option can + be used to override --no-commit. + --squash:: Produce the working tree and index state as if a real merge happened, but do not actually make a commit or @@ -19,6 +23,19 @@ top of the current branch whose effect is the same as merging another branch (or more in case of an octopus). +--no-squash:: + Perform the merge and commit the result. This option can + be used to override --squash. + +--no-ff:: + Generate a merge commit even if the merge resolved as a + fast-forward. + +--ff:: + Do not generate a merge commit if the merge resolved as + a fast-forward, only update the branch pointer. This is + the default behavior of git-merge. + -s <strategy>, \--strategy=<strategy>:: Use the given merge strategy; can be supplied more than once to specify them in the order they should be tried. diff --git a/Documentation/urls.txt b/Documentation/urls.txt index e67f9140ab..4f667382ec 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -36,5 +36,11 @@ To sync with a local directory, you can use: - file:///path/to/repo.git/ =============================================================== +ifndef::git-clone[] They are mostly equivalent, except when cloning. See gitlink:git-clone[1] for details. +endif::git-clone[] + +ifdef::git-clone[] +They are equivalent, except the former implies --local option. +endif::git-clone[] diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index b0a873bd7f..0aaed10c76 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -481,7 +481,7 @@ Bisecting: 3537 revisions left to test after this If you run "git branch" at this point, you'll see that git has temporarily moved you to a new branch named "bisect". This branch points to a commit (with commit id 65934...) that is reachable from -v2.6.19 but not from v2.6.18. Compile and test it, and see whether +"master" but not from v2.6.18. Compile and test it, and see whether it crashes. Assume it does crash. Then: ------------------------------------------------- @@ -939,7 +939,7 @@ file such that it contained the given content either before or after the commit. You can find out with this: ------------------------------------------------- -$ git log --raw --abbrev=40 --pretty=oneline -- filename | +$ git log --raw --abbrev=40 --pretty=oneline | grep -B 1 `git hash-object filename` ------------------------------------------------- @@ -1380,7 +1380,7 @@ If you make a commit that you later wish you hadn't, there are two fundamentally different ways to fix the problem: 1. You can create a new commit that undoes whatever was done - by the previous commit. This is the correct thing if your + by the old commit. This is the correct thing if your mistake has already been made public. 2. You can go back and modify the old commit. You should @@ -1508,7 +1508,7 @@ Ensuring good performance ------------------------- On large repositories, git depends on compression to keep the history -information from taking up to much space on disk or in memory. +information from taking up too much space on disk or in memory. This compression is not performed automatically. Therefore you should occasionally run gitlink:git-gc[1]: @@ -1549,7 +1549,7 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f Dangling objects are not a problem. At worst they may take up a little extra disk space. They can sometimes provide a last-resort method for recovering lost work--see <<dangling-objects>> for details. However, if -you wish, you can remove them with gitlink:git-prune[1] or the --prune +you wish, you can remove them with gitlink:git-prune[1] or the `--prune` option to gitlink:git-gc[1]: ------------------------------------------------- @@ -1573,7 +1573,7 @@ Recovering lost changes Reflogs ^^^^^^^ -Say you modify a branch with gitlink:git-reset[1] --hard, and then +Say you modify a branch with `gitlink:git-reset[1] --hard`, and then realize that the branch was the only reference you had to that point in history. @@ -1585,9 +1585,9 @@ old history using, for example, $ git log master@{1} ------------------------------------------------- -This lists the commits reachable from the previous version of the head. -This syntax can be used to with any git command that accepts a commit, -not just with git log. Some other examples: +This lists the commits reachable from the previous version of the +"master" branch head. This syntax can be used with any git command +that accepts a commit, not just with git log. Some other examples: ------------------------------------------------- $ git show master@{2} # See where the branch pointed 2, @@ -1702,7 +1702,7 @@ $ git pull More generally, a branch that is created from a remote branch will pull by default from that branch. See the descriptions of the branch.<name>.remote and branch.<name>.merge options in -gitlink:git-config[1], and the discussion of the --track option in +gitlink:git-config[1], and the discussion of the `--track` option in gitlink:git-checkout[1], to learn how to control these defaults. In addition to saving you keystrokes, "git pull" also helps you by @@ -1800,7 +1800,7 @@ $ git clone /path/to/repository $ git pull /path/to/other/repository ------------------------------------------------- -or an ssh url: +or an ssh URL: ------------------------------------------------- $ git clone ssh://yourhost/~you/repository @@ -1861,7 +1861,7 @@ Exporting a git repository via the git protocol This is the preferred method. If someone else administers the server, they should tell you what -directory to put the repository in, and what git:// url it will appear +directory to put the repository in, and what git:// URL it will appear at. You can then skip to the section "<<pushing-changes-to-a-public-repository,Pushing changes to a public repository>>", below. @@ -1898,8 +1898,8 @@ $ chmod a+x hooks/post-update gitlink:git-update-server-info[1], and the documentation link:hooks.html[Hooks used by git].) -Advertise the url of proj.git. Anybody else should then be able to -clone or pull from that url, for example with a command line like: +Advertise the URL of proj.git. Anybody else should then be able to +clone or pull from that URL, for example with a command line like: ------------------------------------------------- $ git clone http://yourserver.com/~you/proj.git @@ -2098,7 +2098,7 @@ $ git branch --track test origin/master $ git branch --track release origin/master ------------------------------------------------- -These can be easily kept up to date using gitlink:git-pull[1] +These can be easily kept up to date using gitlink:git-pull[1]. ------------------------------------------------- $ git checkout test && git pull @@ -2190,7 +2190,7 @@ changes are in a specific branch, use: $ git log linux..branchname | git-shortlog ------------------------------------------------- -To see whether it has already been merged into the test or release branches +To see whether it has already been merged into the test or release branches, use: ------------------------------------------------- @@ -2203,12 +2203,12 @@ or $ git log release..branchname ------------------------------------------------- -(If this branch has not yet been merged you will see some log entries. +(If this branch has not yet been merged, you will see some log entries. If it has been merged, then there will be no output.) Once a patch completes the great cycle (moving from test to release, then pulled by Linus, and finally coming back into your local -"origin/master" branch) the branch for this change is no longer needed. +"origin/master" branch), the branch for this change is no longer needed. You detect this when the output from: ------------------------------------------------- @@ -2470,7 +2470,7 @@ $ git rebase --continue and git will continue applying the rest of the patches. -At any point you may use the --abort option to abort this process and +At any point you may use the `--abort` option to abort this process and return mywork to the state it had before you started the rebase: ------------------------------------------------- @@ -2539,9 +2539,9 @@ $ git checkout -b mywork-new origin $ gitk origin..mywork & ------------------------------------------------- -And browse through the list of patches in the mywork branch using gitk, +and browse through the list of patches in the mywork branch using gitk, applying them (possibly in a different order) to mywork-new using -cherry-pick, and possibly modifying them as you go using commit --amend. +cherry-pick, and possibly modifying them as you go using `commit --amend`. The gitlink:git-gui[1] command may also help as it allows you to individually select diff hunks for inclusion in the index (by right-clicking on the diff hunk and choosing "Stage Hunk for Commit"). @@ -2865,7 +2865,7 @@ others: - Git can quickly determine whether two objects are identical or not, just by comparing names. -- Since object names are computed the same way in ever repository, the +- Since object names are computed the same way in every repository, the same content stored in two repositories will always be stored under the same name. - Git can detect errors when it reads an object, by checking that the @@ -2882,7 +2882,7 @@ There are four different types of objects: "blob", "tree", "commit", and "blob" objects into a directory structure. In addition, a tree object can refer to other tree objects, thus creating a directory hierarchy. - A <<def_commit_object,"commit" object>> ties such directory hierarchies - together into a <<def_DAG,directed acyclic graph>> of revisions - each + together into a <<def_DAG,directed acyclic graph>> of revisions--each commit contains the object name of exactly one tree designating the directory hierarchy at the time of the commit. In addition, a commit refers to "parent" commit objects that describe the history of how we @@ -3155,7 +3155,7 @@ There are also other situations that cause dangling objects. For example, a "dangling blob" may arise because you did a "git add" of a file, but then, before you actually committed it and made it part of the bigger picture, you changed something else in that file and committed -that *updated* thing - the old state that you added originally ends up +that *updated* thing--the old state that you added originally ends up not being pointed to by any commit or tree, so it's now a dangling blob object. @@ -3170,7 +3170,7 @@ up pointing to them, so they end up "dangling" in your repository. Generally, dangling objects aren't anything to worry about. They can even be very useful: if you screw something up, the dangling objects can be how you recover your old tree (say, you did a rebase, and realized -that you really didn't want to - you can look at what dangling objects +that you really didn't want to--you can look at what dangling objects you have, and decide to reset your head to some old dangling state). For commits, you can just use: @@ -3214,10 +3214,10 @@ $ git prune ------------------------------------------------ and they'll be gone. But you should only run "git prune" on a quiescent -repository - it's kind of like doing a filesystem fsck recovery: you +repository--it's kind of like doing a filesystem fsck recovery: you don't want to do that while the filesystem is mounted. -(The same is true of "git-fsck" itself, btw - but since +(The same is true of "git-fsck" itself, btw, but since git-fsck never actually *changes* the repository, it just reports on what it found, git-fsck itself is never "dangerous" to run. Running it while somebody is actually changing the repository can cause @@ -3336,14 +3336,14 @@ $ git hash-object -w <recreated-file> and your repository is good again! -(Btw, you could have ignored the fsck, and started with doing a +(Btw, you could have ignored the fsck, and started with doing a ------------------------------------------------ $ git log --raw --all ------------------------------------------------ -and just looked for the sha of the missing object (4b9458b..) in that -whole thing. It's up to you - git does *have* a lot of information, it is +and just looked for the sha of the missing object (4b9458b..) in that +whole thing. It's up to you - git does *have* a lot of information, it is just missing one particular blob version. [[the-index]] @@ -3672,9 +3672,10 @@ The Workflow ------------ High-level operations such as gitlink:git-commit[1], -gitlink:git-checkout[1] and git-reset[1] work by moving data between the -working tree, the index, and the object database. Git provides -low-level operations which perform each of these steps individually. +gitlink:git-checkout[1] and gitlink:git-reset[1] work by moving data +between the working tree, the index, and the object database. Git +provides low-level operations which perform each of these steps +individually. Generally, all "git" operations work on the index file. Some operations work *purely* on the index file (showing the current state of the @@ -3729,7 +3730,7 @@ You write your current index file to a "tree" object with the program $ git write-tree ------------------------------------------------- -that doesn't come with any options - it will just write out the +that doesn't come with any options--it will just write out the current index into the set of tree objects that describe that state, and it will return the name of the resulting top-level tree. You can use that tree to re-generate the index at any time by going in the @@ -3740,7 +3741,7 @@ object database -> index ~~~~~~~~~~~~~~~~~~~~~~~~ You read a "tree" file from the object database, and use that to -populate (and overwrite - don't do this if your index contains any +populate (and overwrite--don't do this if your index contains any unsaved state that you might want to restore later!) your current index. Normal operation is just @@ -3788,7 +3789,7 @@ Tying it all together To commit a tree you have instantiated with "git-write-tree", you'd create a "commit" object that refers to that tree and the history -behind it - most notably the "parent" commits that preceded it in +behind it--most notably the "parent" commits that preceded it in history. Normally a "commit" has one parent: the previous state of the tree @@ -3931,7 +3932,7 @@ Once you know the three trees you are going to merge (the one "original" tree, aka the common tree, and the two "result" trees, aka the branches you want to merge), you do a "merge" read into the index. This will complain if it has to throw away your old index contents, so you should -make sure that you've committed those - in fact you would normally +make sure that you've committed those--in fact you would normally always do a merge against your last commit (which should thus match what you have in your current index anyway). @@ -3951,7 +3952,7 @@ Merging multiple trees, continued --------------------------------- Sadly, many merges aren't trivial. If there are files that have -been added.moved or removed, or if both branches have modified the +been added, moved or removed, or if both branches have modified the same file, you will be left with an index tree that contains "merge entries" in it. Such an index tree can 'NOT' be written out to a tree object, and you will have to resolve any such merge clashes using @@ -4203,7 +4204,7 @@ Two things are interesting here: - `get_sha1()` returns 0 on _success_. This might surprise some new Git hackers, but there is a long tradition in UNIX to return different - negative numbers in case of different errors -- and 0 on success. + negative numbers in case of different errors--and 0 on success. - the variable `sha1` in the function signature of `get_sha1()` is `unsigned char \*`, but is actually expected to be a pointer to `unsigned @@ -4308,7 +4309,7 @@ $ git branch new # create branch "new" starting at current HEAD $ git branch -d new # delete branch "new" ----------------------------------------------- -Instead of basing new branch on current HEAD (the default), use: +Instead of basing a new branch on current HEAD (the default), use: ----------------------------------------------- $ git branch new test # branch named "test" diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index fa38f373ac..3c0032cec5 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.3.6.GIT +DEF_VER=v1.5.3.GIT LF=' ' @@ -28,6 +28,8 @@ all:: # # Define NO_STRCASESTR if you don't have strcasestr. # +# Define NO_MEMMEM if you don't have memmem. +# # Define NO_STRLCPY if you don't have strlcpy. # # Define NO_STRTOUMAX if you don't have strtoumax in the C library. @@ -36,6 +38,8 @@ all:: # # Define NO_SETENV if you don't have setenv in the C library. # +# Define NO_MKDTEMP if you don't have mkdtemp in the C library. +# # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # @@ -126,6 +130,9 @@ all:: # If not set it defaults to the bare 'wish'. If it is set to the empty # string then NO_TCLTK will be forced (this is used by configure script). # +# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit +# parallel delta searching when packing objects. +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -164,6 +171,7 @@ GITWEB_CONFIG = gitweb_config.perl GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git +GITWEB_PROJECT_MAXDEPTH = 2007 GITWEB_EXPORT_OK = GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = @@ -205,16 +213,14 @@ BASIC_LDFLAGS = SCRIPT_SH = \ git-bisect.sh git-checkout.sh \ - git-clean.sh git-clone.sh git-commit.sh \ - git-fetch.sh \ - git-ls-remote.sh \ + git-clone.sh git-commit.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ - git-repack.sh git-request-pull.sh git-reset.sh \ + git-repack.sh git-request-pull.sh \ git-sh-setup.sh \ git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ - git-merge-resolve.sh git-merge-ours.sh \ + git-merge-resolve.sh \ git-lost-found.sh git-quiltimport.sh git-submodule.sh \ git-filter-branch.sh \ git-stash.sh @@ -222,8 +228,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl git-remote.perl \ - git-svnimport.perl git-cvsexportcommit.perl \ + git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -232,15 +237,15 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-convert-objects$X git-fetch-pack$X \ - git-hash-object$X git-index-pack$X git-local-fetch$X \ + git-fetch-pack$X \ + git-hash-object$X git-index-pack$X \ git-fast-import$X \ git-daemon$X \ git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ - git-peek-remote$X git-receive-pack$X \ + git-receive-pack$X \ git-send-pack$X git-shell$X \ - git-show-index$X git-ssh-fetch$X \ - git-ssh-upload$X git-unpack-file$X \ + git-show-index$X \ + git-unpack-file$X \ git-update-server-info$X \ git-upload-pack$X \ git-pack-redundant$X git-var$X \ @@ -268,9 +273,6 @@ ifndef NO_TCLTK OTHER_PROGRAMS += gitk-wish endif -# Backward compatibility -- to be removed after 1.0 -PROGRAMS += git-ssh-pull$X git-ssh-push$X - # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -290,7 +292,7 @@ LIB_H = \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \ - mailmap.h remote.h + mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -300,7 +302,7 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o \ - interpolate.o \ + pretty.o interpolate.o hash.o \ lockfile.o \ patch-ids.o \ object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \ @@ -312,7 +314,8 @@ LIB_OBJS = \ write_or_die.o trace.o list-objects.o grep.o match-trees.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ - convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o + convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \ + transport.o bundle.o walker.o parse-options.o BUILTIN_OBJS = \ builtin-add.o \ @@ -326,6 +329,7 @@ BUILTIN_OBJS = \ builtin-check-attr.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ + builtin-clean.o \ builtin-commit-tree.o \ builtin-count-objects.o \ builtin-describe.o \ @@ -333,6 +337,8 @@ BUILTIN_OBJS = \ builtin-diff-files.o \ builtin-diff-index.o \ builtin-diff-tree.o \ + builtin-fetch.o \ + builtin-fetch-pack.o \ builtin-fetch--tool.o \ builtin-fmt-merge-msg.o \ builtin-for-each-ref.o \ @@ -343,10 +349,12 @@ BUILTIN_OBJS = \ builtin-log.o \ builtin-ls-files.o \ builtin-ls-tree.o \ + builtin-ls-remote.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ builtin-merge-base.o \ builtin-merge-file.o \ + builtin-merge-ours.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ @@ -355,8 +363,10 @@ BUILTIN_OBJS = \ builtin-push.o \ builtin-read-tree.o \ builtin-reflog.o \ + builtin-send-pack.o \ builtin-config.o \ builtin-rerere.o \ + builtin-reset.o \ builtin-rev-list.o \ builtin-rev-parse.o \ builtin-revert.o \ @@ -400,13 +410,16 @@ ifeq ($(uname_S),Darwin) NEEDS_LIBICONV = YesPlease OLD_ICONV = UnfortunatelyYes NO_STRLCPY = YesPlease + NO_MEMMEM = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NO_HSTRERROR = YesPlease + NO_MKDTEMP = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease NO_UNSETENV = YesPlease @@ -428,6 +441,7 @@ ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease NO_D_INO_IN_DIRENT = YesPlease NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NO_SYMLINK_HEAD = YesPlease NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes @@ -441,11 +455,13 @@ ifeq ($(uname_O),Cygwin) endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease + NO_MEMMEM = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib @@ -460,6 +476,7 @@ ifeq ($(uname_S),NetBSD) endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease + NO_MEMMEM = YesPlease NO_STRLCPY = YesPlease NEEDS_LIBICONV=YesPlease endif @@ -471,6 +488,7 @@ ifeq ($(uname_S),IRIX64) NO_IPV6=YesPlease NO_SETENV=YesPlease NO_STRCASESTR=YesPlease + NO_MEMMEM = YesPlease NO_STRLCPY = YesPlease NO_SOCKADDR_STORAGE=YesPlease SHELL_PATH=/usr/gnu/bin/bash @@ -508,7 +526,9 @@ else CC_LD_DYNPATH = -R endif -ifndef NO_CURL +ifdef NO_CURL + BASIC_CFLAGS += -DNO_CURL +else ifdef CURLDIR # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case. BASIC_CFLAGS += -I$(CURLDIR)/include @@ -516,7 +536,9 @@ ifndef NO_CURL else CURL_LIBCURL = -lcurl endif - PROGRAMS += git-http-fetch$X + BUILTIN_OBJS += builtin-http-fetch.o + EXTLIBS += $(CURL_LIBCURL) + LIB_OBJS += http.o http-walker.o curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT @@ -598,6 +620,10 @@ ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o endif +ifdef NO_MKDTEMP + COMPAT_CFLAGS += -DNO_MKDTEMP + COMPAT_OBJS += compat/mkdtemp.o +endif ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV COMPAT_OBJS += compat/unsetenv.o @@ -669,6 +695,15 @@ ifdef NO_HSTRERROR COMPAT_CFLAGS += -DNO_HSTRERROR COMPAT_OBJS += compat/hstrerror.o endif +ifdef NO_MEMMEM + COMPAT_CFLAGS += -DNO_MEMMEM + COMPAT_OBJS += compat/memmem.o +endif + +ifdef THREADED_DELTA_SEARCH + BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH + EXTLIBS += -lpthread +endif ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks @@ -721,7 +756,7 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ - -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS) + $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) ALL_CFLAGS += $(BASIC_CFLAGS) @@ -817,6 +852,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ @@ -869,39 +905,30 @@ exec_cmd.o: exec_cmd.c GIT-CFLAGS builtin-init-db.o: builtin-init-db.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $< +config.o: config.c GIT-CFLAGS + $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $< + http.o: http.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $< ifdef NO_EXPAT -http-fetch.o: http-fetch.c http.h GIT-CFLAGS +http-walker.o: http-walker.c http.h GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $< endif git-%$X: %.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) -ssh-pull.o: ssh-fetch.c -ssh-push.o: ssh-upload.c -git-local-fetch$X: fetch.o -git-ssh-fetch$X: rsh.o fetch.o -git-ssh-upload$X: rsh.o -git-ssh-pull$X: rsh.o fetch.o -git-ssh-push$X: rsh.o - git-imap-send$X: imap-send.o $(LIB_FILE) -http.o http-fetch.o http-push.o: http.h -git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +http.o http-walker.o http-push.o: http.h git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -$(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H) +$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) -$(DIFF_OBJS): diffcore.h builtin-revert.o builtin-runstatus.o wt-status.o: wt-status.h $(LIB_FILE): $(LIB_OBJS) @@ -930,6 +957,10 @@ tags: $(RM) tags $(FIND) . -name '*.[hcS]' -print | xargs ctags -a +cscope: + $(RM) cscope* + $(FIND) . -name '*.[hcS]' -print | xargs cscope -b + ### Detect prefix changes TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) @@ -957,7 +988,7 @@ endif ### Testing rules -TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X +TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X all:: $(TEST_PROGRAMS) @@ -974,6 +1005,8 @@ test-date$X: date.o ctype.o test-delta$X: diff-delta.o patch-delta.o +test-parse-options$X: parse-options.o + .PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) test-%$X: test-%.o $(GITLIBS) @@ -1079,7 +1112,7 @@ clean: $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) - $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags + $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache $(RM) -r $(GIT_TARNAME) .doc-tmp-dir @@ -1097,19 +1130,19 @@ endif $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS .PHONY: all install clean strip -.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS +.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS ### Check documentation # check-docs:: - @for v in $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk; \ + @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \ do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ - git-merge-resolve | git-merge-stupid | \ + git-merge-resolve | git-merge-stupid | git-merge-subtree | \ git-add--interactive | git-fsck-objects | git-init-db | \ - git-repo-config | git-fetch--tool | \ - git-ssh-pull | git-ssh-push ) continue ;; \ + git-rebase--interactive | \ + git-repo-config | git-fetch--tool ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ echo "no doc: $$v"; \ @@ -1119,7 +1152,30 @@ check-docs:: git) ;; \ *) echo "no link: $$v";; \ esac ; \ - done | sort + done; \ + ( \ + sed -e '1,/^__DATA__/d' \ + -e 's/[ ].*//' \ + -e 's/^/listed /' Documentation/cmd-list.perl; \ + ls -1 Documentation/git*txt | \ + sed -e 's|Documentation/|documented |' \ + -e 's/\.txt//'; \ + ) | while read how cmd; \ + do \ + case "$$how,$$cmd" in \ + *,git-citool | \ + *,git-gui | \ + documented,gitattributes | \ + documented,gitignore | \ + documented,gitmodules | \ + documented,git-tools | \ + sentinel,not,matching,is,ok ) continue ;; \ + esac; \ + case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \ + *" $$cmd "*) ;; \ + *) echo "removed but $$how: $$cmd" ;; \ + esac; \ + done ) | sort ### Make sure built-ins do not have dups and listed in git.c # @@ -1 +1 @@ -Documentation/RelNotes-1.5.3.6.txt
\ No newline at end of file +Documentation/RelNotes-1.5.4.txt
\ No newline at end of file diff --git a/archive-tar.c b/archive-tar.c index 66fe3e375b..e1bced5609 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -3,7 +3,6 @@ */ #include "cache.h" #include "commit.h" -#include "strbuf.h" #include "tar.h" #include "builtin.h" #include "archive.h" @@ -17,6 +16,7 @@ static unsigned long offset; static time_t archive_time; static int tar_umask = 002; static int verbose; +static const struct commit *commit; /* writes out the whole block, but only if it is full */ static void write_if_needed(void) @@ -78,19 +78,6 @@ static void write_trailer(void) } } -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total + 1 > sb->alloc) { - sb->buf = xrealloc(sb->buf, total + 1); - sb->alloc = total + 1; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; - sb->buf[total] = '\0'; -} - /* * pax extended header records have the format "%u %s=%s\n". %u contains * the size of the whole string (including the %u), the first %s is the @@ -100,26 +87,17 @@ static void strbuf_append_string(struct strbuf *sb, const char *s) static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, const char *value, unsigned int valuelen) { - char *p; - int len, total, tmp; + int len, tmp; /* "%u %s=%s\n" */ len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; for (tmp = len; tmp > 9; tmp /= 10) len++; - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; + strbuf_grow(sb, len); + strbuf_addf(sb, "%u %s=", len, keyword); + strbuf_add(sb, value, valuelen); + strbuf_addch(sb, '\n'); } static unsigned int ustar_header_chksum(const struct ustar_header *header) @@ -153,8 +131,7 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, struct strbuf ext_header; memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; + strbuf_init(&ext_header, 0); if (!sha1) { *header.typeflag = TYPEFLAG_GLOBAL_HEADER; @@ -166,7 +143,7 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); } else { if (verbose) - fprintf(stderr, "%.*s\n", path->len, path->buf); + fprintf(stderr, "%.*s\n", (int)path->len, path->buf); if (S_ISDIR(mode) || S_ISGITLINK(mode)) { *header.typeflag = TYPEFLAG_DIR; mode = (mode | 0777) & ~tar_umask; @@ -225,8 +202,8 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, if (ext_header.len > 0) { write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); } + strbuf_release(&ext_header); write_blocked(&header, sizeof(header)); if (S_ISREG(mode) && buffer && size > 0) write_blocked(buffer, size); @@ -235,11 +212,11 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, static void write_global_extended_header(const unsigned char *sha1) { struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; + + strbuf_init(&ext_header, 0); strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); + strbuf_release(&ext_header); } static int git_tar_config(const char *var, const char *value) @@ -260,32 +237,22 @@ static int write_tar_entry(const unsigned char *sha1, const char *base, int baselen, const char *filename, unsigned mode, int stage) { - static struct strbuf path; - int filenamelen = strlen(filename); + static struct strbuf path = STRBUF_INIT; void *buffer; enum object_type type; unsigned long size; - if (!path.alloc) { - path.buf = xmalloc(PATH_MAX); - path.alloc = PATH_MAX; - path.len = path.eof = 0; - } - if (path.alloc < baselen + filenamelen + 1) { - free(path.buf); - path.buf = xmalloc(baselen + filenamelen + 1); - path.alloc = baselen + filenamelen + 1; - } - memcpy(path.buf, base, baselen); - memcpy(path.buf + baselen, filename, filenamelen); - path.len = baselen + filenamelen; - path.buf[path.len] = '\0'; + strbuf_reset(&path); + strbuf_grow(&path, PATH_MAX); + strbuf_add(&path, base, baselen); + strbuf_addstr(&path, filename); if (S_ISDIR(mode) || S_ISGITLINK(mode)) { - strbuf_append_string(&path, "/"); + strbuf_addch(&path, '/'); buffer = NULL; size = 0; } else { - buffer = convert_sha1_file(path.buf, sha1, mode, &type, &size); + buffer = sha1_file_to_archive(path.buf, sha1, mode, &type, + &size, commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); } @@ -304,6 +271,7 @@ int write_tar_archive(struct archiver_args *args) archive_time = args->time; verbose = args->verbose; + commit = args->commit; if (args->commit_sha1) write_global_extended_header(args->commit_sha1); diff --git a/archive-zip.c b/archive-zip.c index 444e1623db..74e30f6205 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -12,6 +12,7 @@ static int verbose; static int zip_date; static int zip_time; +static const struct commit *commit; static unsigned char *zip_dir; static unsigned int zip_dir_size; @@ -191,11 +192,13 @@ static int write_zip_entry(const unsigned char *sha1, compressed_size = 0; } else if (S_ISREG(mode) || S_ISLNK(mode)) { method = 0; - attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0; + attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : + (mode & 0111) ? ((mode) << 16) : 0; if (S_ISREG(mode) && zlib_compression_level != 0) method = 8; result = 0; - buffer = convert_sha1_file(path, sha1, mode, &type, &size); + buffer = sha1_file_to_archive(path, sha1, mode, &type, &size, + commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); crc = crc32(crc, buffer, size); @@ -229,7 +232,8 @@ static int write_zip_entry(const unsigned char *sha1, } copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0); + copy_le16(dirent.creator_version, + S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0); copy_le16(dirent.version, 10); copy_le16(dirent.flags, 0); copy_le16(dirent.compression_method, method); @@ -316,6 +320,7 @@ int write_zip_archive(struct archiver_args *args) zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; verbose = args->verbose; + commit = args->commit; if (args->base && plen > 0 && args->base[plen - 1] == '/') { char *base = xstrdup(args->base); @@ -8,6 +8,7 @@ struct archiver_args { const char *base; struct tree *tree; const unsigned char *commit_sha1; + const struct commit *commit; time_t time; const char **pathspec; unsigned int verbose : 1; @@ -42,4 +43,6 @@ extern int write_tar_archive(struct archiver_args *); extern int write_zip_archive(struct archiver_args *); extern void *parse_extra_zip_args(int argc, const char **argv); +extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit); + #endif /* ARCHIVE_H */ @@ -160,12 +160,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, else if (!equals) e->setto = ATTR__TRUE; else { - char *value; - int vallen = ep - equals; - value = xmalloc(vallen); - memcpy(value, equals+1, vallen-1); - value[vallen-1] = 0; - e->setto = value; + e->setto = xmemdupz(equals + 1, ep - equals - 1); } e->attr = git_attr(cp, len); } diff --git a/builtin-add.c b/builtin-add.c index 4fc9d6f7cc..cf815a0b8e 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -12,9 +12,13 @@ #include "diffcore.h" #include "commit.h" #include "revision.h" +#include "run-command.h" +#include "parse-options.h" -static const char builtin_add_usage[] = -"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>..."; +static const char * const builtin_add_usage[] = { + "git-add [options] [--] <filepattern>...", + NULL +}; static int take_worktree_changes; @@ -66,12 +70,8 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec, baselen = common_prefix(pathspec); path = "."; base = ""; - if (baselen) { - char *common = xmalloc(baselen + 1); - memcpy(common, *pathspec, baselen); - common[baselen] = 0; - path = base = common; - } + if (baselen) + path = base = xmemdupz(*pathspec, baselen); /* Read the directory and prune it */ read_directory(dir, path, base, baselen, pathspec); @@ -98,7 +98,6 @@ static void update_callback(struct diff_queue_struct *q, break; case DIFF_STATUS_DELETED: remove_file_from_cache(path); - cache_tree_invalidate_path(active_cache_tree, path); if (verbose) printf("remove '%s'\n", path); break; @@ -106,7 +105,7 @@ static void update_callback(struct diff_queue_struct *q, } } -static void update(int verbose, const char *prefix, const char **files) +void add_files_to_cache(int verbose, const char *prefix, const char **files) { struct rev_info rev; init_revisions(&rev, prefix); @@ -115,8 +114,6 @@ static void update(int verbose, const char *prefix, const char **files) rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &verbose; - if (read_cache() < 0) - die("index file corrupt"); run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); } @@ -138,80 +135,63 @@ static void refresh(int verbose, const char **pathspec) free(seen); } +int interactive_add(void) +{ + const char *argv[2] = { "add--interactive", NULL }; + + return run_command_v_opt(argv, RUN_GIT_CMD); +} + static struct lock_file lock_file; static const char ignore_error[] = "The following paths are ignored by one of your .gitignore files:\n"; +static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; +static int add_interactive = 0; + +static struct option builtin_add_options[] = { + OPT__DRY_RUN(&show_only), + OPT__VERBOSE(&verbose), + OPT_GROUP(""), + OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), + OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"), + OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"), + OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), + OPT_END(), +}; + int cmd_add(int argc, const char **argv, const char *prefix) { - int i, newfd; - int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; + int i, newfd, orig_argc = argc; const char **pathspec; struct dir_struct dir; - int add_interactive = 0; - for (i = 1; i < argc; i++) { - if (!strcmp("--interactive", argv[i]) || - !strcmp("-i", argv[i])) - add_interactive++; - } + argc = parse_options(argc, argv, builtin_add_options, + builtin_add_usage, 0); if (add_interactive) { - const char *args[] = { "add--interactive", NULL }; - - if (add_interactive != 1 || argc != 2) + if (add_interactive != 1 || orig_argc != 2) die("add --interactive does not take any parameters"); - execv_git_cmd(args); - exit(1); + exit(interactive_add()); } git_config(git_default_config); newfd = hold_locked_index(&lock_file, 1); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-n")) { - show_only = 1; - continue; - } - if (!strcmp(arg, "-f")) { - ignored_too = 1; - continue; - } - if (!strcmp(arg, "-v")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "-u")) { - take_worktree_changes = 1; - continue; - } - if (!strcmp(arg, "--refresh")) { - refresh_only = 1; - continue; - } - usage(builtin_add_usage); - } - if (take_worktree_changes) { - update(verbose, prefix, argv + i); + if (read_cache() < 0) + die("index file corrupt"); + add_files_to_cache(verbose, prefix, argv); goto finish; } - if (argc <= i) { + if (argc == 0) { fprintf(stderr, "Nothing specified, nothing added.\n"); fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); return 0; } - pathspec = get_pathspec(prefix, argv + i); + pathspec = get_pathspec(prefix, argv); if (refresh_only) { refresh(verbose, pathspec); diff --git a/builtin-apply.c b/builtin-apply.c index 0fff02e0d8..91f8752ff7 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -152,7 +152,7 @@ struct patch { unsigned int is_rename:1; struct fragment *fragments; char *result; - unsigned long resultsize; + size_t resultsize; char old_sha1_prefix[41]; char new_sha1_prefix[41]; struct patch *next; @@ -163,15 +163,14 @@ static void say_patch_name(FILE *output, const char *pre, struct patch *patch, c fputs(pre, output); if (patch->old_name && patch->new_name && strcmp(patch->old_name, patch->new_name)) { - write_name_quoted(NULL, 0, patch->old_name, 1, output); + quote_c_style(patch->old_name, NULL, output, 0); fputs(" => ", output); - write_name_quoted(NULL, 0, patch->new_name, 1, output); - } - else { + quote_c_style(patch->new_name, NULL, output, 0); + } else { const char *n = patch->new_name; if (!n) n = patch->old_name; - write_name_quoted(NULL, 0, n, 1, output); + quote_c_style(n, NULL, output, 0); } fputs(post, output); } @@ -179,36 +178,18 @@ static void say_patch_name(FILE *output, const char *pre, struct patch *patch, c #define CHUNKSIZE (8192) #define SLOP (16) -static void *read_patch_file(int fd, unsigned long *sizep) +static void read_patch_file(struct strbuf *sb, int fd) { - unsigned long size = 0, alloc = CHUNKSIZE; - void *buffer = xmalloc(alloc); - - for (;;) { - ssize_t nr = alloc - size; - if (nr < 1024) { - alloc += CHUNKSIZE; - buffer = xrealloc(buffer, alloc); - nr = alloc - size; - } - nr = xread(fd, (char *) buffer + size, nr); - if (!nr) - break; - if (nr < 0) - die("git-apply: read returned %s", strerror(errno)); - size += nr; - } - *sizep = size; + if (strbuf_read(sb, fd, 0) < 0) + die("git-apply: read returned %s", strerror(errno)); /* * Make sure that we have some slop in the buffer * so that we can do speculative "memcmp" etc, and * see to it that it is NUL-filled. */ - if (alloc < size + SLOP) - buffer = xrealloc(buffer, size + SLOP); - memset((char *) buffer + size, 0, SLOP); - return buffer; + strbuf_grow(sb, SLOP); + memset(sb->buf + sb->len, 0, SLOP); } static unsigned long linelen(const char *buffer, unsigned long size) @@ -244,35 +225,33 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) { int len; const char *start = line; - char *name; if (*line == '"') { + struct strbuf name; + /* Proposed "new-style" GNU patch/diff format; see * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 */ - name = unquote_c_style(line, NULL); - if (name) { - char *cp = name; - while (p_value) { + strbuf_init(&name, 0); + if (!unquote_c_style(&name, line, NULL)) { + char *cp; + + for (cp = name.buf; p_value; p_value--) { cp = strchr(cp, '/'); if (!cp) break; cp++; - p_value--; } if (cp) { /* name can later be freed, so we need * to memmove, not just return cp */ - memmove(name, cp, strlen(cp) + 1); + strbuf_remove(&name, 0, cp - name.buf); free(def); - return name; - } - else { - free(name); - name = NULL; + return strbuf_detach(&name, NULL); } } + strbuf_release(&name); } for (;;) { @@ -304,13 +283,10 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) int deflen = strlen(def); if (deflen < len && !strncmp(start, def, deflen)) return def; + free(def); } - name = xmalloc(len + 1); - memcpy(name, start, len); - name[len] = 0; - free(def); - return name; + return xmemdupz(start, len); } static int count_slashes(const char *cp) @@ -583,29 +559,30 @@ static const char *stop_at_slash(const char *line, int llen) */ static char *git_header_name(char *line, int llen) { - int len; const char *name; const char *second = NULL; + size_t len; line += strlen("diff --git "); llen -= strlen("diff --git "); if (*line == '"') { const char *cp; - char *first = unquote_c_style(line, &second); - if (!first) - return NULL; + struct strbuf first; + struct strbuf sp; + + strbuf_init(&first, 0); + strbuf_init(&sp, 0); + + if (unquote_c_style(&first, line, &second)) + goto free_and_fail1; /* advance to the first slash */ - cp = stop_at_slash(first, strlen(first)); - if (!cp || cp == first) { - /* we do not accept absolute paths */ - free_first_and_fail: - free(first); - return NULL; - } - len = strlen(cp+1); - memmove(first, cp+1, len+1); /* including NUL */ + cp = stop_at_slash(first.buf, first.len); + /* we do not accept absolute paths */ + if (!cp || cp == first.buf) + goto free_and_fail1; + strbuf_remove(&first, 0, cp + 1 - first.buf); /* second points at one past closing dq of name. * find the second name. @@ -614,40 +591,40 @@ static char *git_header_name(char *line, int llen) second++; if (line + llen <= second) - goto free_first_and_fail; + goto free_and_fail1; if (*second == '"') { - char *sp = unquote_c_style(second, NULL); - if (!sp) - goto free_first_and_fail; - cp = stop_at_slash(sp, strlen(sp)); - if (!cp || cp == sp) { - free_both_and_fail: - free(sp); - goto free_first_and_fail; - } + if (unquote_c_style(&sp, second, NULL)) + goto free_and_fail1; + cp = stop_at_slash(sp.buf, sp.len); + if (!cp || cp == sp.buf) + goto free_and_fail1; /* They must match, otherwise ignore */ - if (strcmp(cp+1, first)) - goto free_both_and_fail; - free(sp); - return first; + if (strcmp(cp + 1, first.buf)) + goto free_and_fail1; + strbuf_release(&sp); + return strbuf_detach(&first, NULL); } /* unquoted second */ cp = stop_at_slash(second, line + llen - second); if (!cp || cp == second) - goto free_first_and_fail; + goto free_and_fail1; cp++; - if (line + llen - cp != len + 1 || - memcmp(first, cp, len)) - goto free_first_and_fail; - return first; + if (line + llen - cp != first.len + 1 || + memcmp(first.buf, cp, first.len)) + goto free_and_fail1; + return strbuf_detach(&first, NULL); + + free_and_fail1: + strbuf_release(&first); + strbuf_release(&sp); + return NULL; } /* unquoted first name */ name = stop_at_slash(line, llen); if (!name || name == line) return NULL; - name++; /* since the first name is unquoted, a dq if exists must be @@ -655,28 +632,30 @@ static char *git_header_name(char *line, int llen) */ for (second = name; second < line + llen; second++) { if (*second == '"') { - const char *cp = second; + struct strbuf sp; const char *np; - char *sp = unquote_c_style(second, NULL); - - if (!sp) - return NULL; - np = stop_at_slash(sp, strlen(sp)); - if (!np || np == sp) { - free_second_and_fail: - free(sp); - return NULL; - } + + strbuf_init(&sp, 0); + if (unquote_c_style(&sp, second, NULL)) + goto free_and_fail2; + + np = stop_at_slash(sp.buf, sp.len); + if (!np || np == sp.buf) + goto free_and_fail2; np++; - len = strlen(np); - if (len < cp - name && + + len = sp.buf + sp.len - np; + if (len < second - name && !strncmp(np, name, len) && isspace(name[len])) { /* Good */ - memmove(sp, np, len + 1); - return sp; + strbuf_remove(&sp, 0, np - sp.buf); + return strbuf_detach(&sp, NULL); } - goto free_second_and_fail; + + free_and_fail2: + strbuf_release(&sp); + return NULL; } } @@ -700,14 +679,10 @@ static char *git_header_name(char *line, int llen) break; } if (second[len] == '\n' && !memcmp(name, second, len)) { - char *ret = xmalloc(len + 1); - memcpy(ret, name, len); - ret[len] = 0; - return ret; + return xmemdupz(name, len); } } } - return NULL; } /* Verify that we recognize the lines following a git header */ @@ -1397,96 +1372,66 @@ static const char minuses[]= "-------------------------------------------------- static void show_stats(struct patch *patch) { - const char *prefix = ""; - char *name = patch->new_name; - char *qname = NULL; - int len, max, add, del, total; - - if (!name) - name = patch->old_name; + struct strbuf qname; + char *cp = patch->new_name ? patch->new_name : patch->old_name; + int max, add, del; - if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { - qname = xmalloc(len + 1); - quote_c_style(name, qname, NULL, 0); - name = qname; - } + strbuf_init(&qname, 0); + quote_c_style(cp, &qname, NULL, 0); /* * "scale" the filename */ - len = strlen(name); max = max_len; if (max > 50) max = 50; - if (len > max) { - char *slash; - prefix = "..."; - max -= 3; - name += len - max; - slash = strchr(name, '/'); - if (slash) - name = slash; + + if (qname.len > max) { + cp = strchr(qname.buf + qname.len + 3 - max, '/'); + if (!cp) + cp = qname.buf + qname.len + 3 - max; + strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); + } + + if (patch->is_binary) { + printf(" %-*s | Bin\n", max, qname.buf); + strbuf_release(&qname); + return; } - len = max; + + printf(" %-*s |", max, qname.buf); + strbuf_release(&qname); /* * scale the add/delete */ - max = max_change; - if (max + len > 70) - max = 70 - len; - + max = max + max_change > 70 ? 70 - max : max_change; add = patch->lines_added; del = patch->lines_deleted; - total = add + del; if (max_change > 0) { - total = (total * max + max_change / 2) / max_change; + int total = ((add + del) * max + max_change / 2) / max_change; add = (add * max + max_change / 2) / max_change; del = total - add; } - if (patch->is_binary) - printf(" %s%-*s | Bin\n", prefix, len, name); - else - printf(" %s%-*s |%5d %.*s%.*s\n", prefix, - len, name, patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); - free(qname); + printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, + add, pluses, del, minuses); } -static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p) +static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) { - int fd; - unsigned long got; - unsigned long nsize; - char *nbuf; - unsigned long size = *size_p; - char *buf = *buf_p; - switch (st->st_mode & S_IFMT) { case S_IFLNK: - return readlink(path, buf, size) != size; + strbuf_grow(buf, st->st_size); + if (readlink(path, buf->buf, st->st_size) != st->st_size) + return -1; + strbuf_setlen(buf, st->st_size); + return 0; case S_IFREG: - fd = open(path, O_RDONLY); - if (fd < 0) - return error("unable to open %s", path); - got = 0; - for (;;) { - ssize_t ret = xread(fd, buf + got, size - got); - if (ret <= 0) - break; - got += ret; - } - close(fd); - nsize = got; - nbuf = convert_to_git(path, buf, &nsize); - if (nbuf) { - free(buf); - *buf_p = nbuf; - *alloc_p = nsize; - *size_p = nsize; - } - return got != size; + if (strbuf_read_file(buf, path, st->st_size) != st->st_size) + return error("unable to open or read %s", path); + convert_to_git(path, buf->buf, buf->len, buf); + return 0; default: return -1; } @@ -1591,12 +1536,6 @@ static void remove_last_line(const char **rbuf, int *rsize) *rsize = offset + 1; } -struct buffer_desc { - char *buffer; - unsigned long size; - unsigned long alloc; -}; - static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, @@ -1673,10 +1612,9 @@ static int apply_line(char *output, const char *patch, int plen) return output + plen - buf; } -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) +static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int inaccurate_eof) { int match_beginning, match_end; - char *buf = desc->buffer; const char *patch = frag->patch; int offset, size = frag->size; char *old = xmalloc(size); @@ -1787,24 +1725,17 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, + offset = find_offset(buf->buf, buf->len, oldlines, oldsize, pos, &lines); - if (match_end && offset + oldsize != desc->size) + if (match_end && offset + oldsize != buf->len) offset = -1; if (match_beginning && offset) offset = -1; if (offset >= 0) { - int diff; - unsigned long size, alloc; - if (new_whitespace == strip_whitespace && - (desc->size - oldsize - offset == 0)) /* end of file? */ + (buf->len - oldsize - offset == 0)) /* end of file? */ newsize -= new_blank_lines_at_end; - diff = newsize - oldsize; - size = desc->size + diff; - alloc = desc->alloc; - /* Warn if it was necessary to reduce the number * of context lines. */ @@ -1814,19 +1745,8 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i " to apply fragment at %d\n", leading, trailing, pos + lines); - if (size > alloc) { - alloc = size + 8192; - desc->alloc = alloc; - buf = xrealloc(buf, alloc); - desc->buffer = buf; - } - desc->size = size; - memmove(buf + offset + newsize, - buf + offset + oldsize, - size - offset - newsize); - memcpy(buf + offset, newlines, newsize); + strbuf_splice(buf, offset, oldsize, newlines, newsize); offset = 0; - break; } @@ -1862,12 +1782,11 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i return offset; } -static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) +static int apply_binary_fragment(struct strbuf *buf, struct patch *patch) { - unsigned long dst_size; struct fragment *fragment = patch->fragments; - void *data; - void *result; + unsigned long len; + void *dst; /* Binary patch is irreversible without the optional second hunk */ if (apply_in_reverse) { @@ -1878,29 +1797,24 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) ? patch->new_name : patch->old_name); fragment = fragment->next; } - data = (void*) fragment->patch; switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: - result = patch_delta(desc->buffer, desc->size, - data, - fragment->size, - &dst_size); - free(desc->buffer); - desc->buffer = result; - break; + dst = patch_delta(buf->buf, buf->len, fragment->patch, + fragment->size, &len); + if (!dst) + return -1; + /* XXX patch_delta NUL-terminates */ + strbuf_attach(buf, dst, len, len + 1); + return 0; case BINARY_LITERAL_DEFLATED: - free(desc->buffer); - desc->buffer = data; - dst_size = fragment->size; - break; + strbuf_reset(buf); + strbuf_add(buf, fragment->patch, fragment->size); + return 0; } - if (!desc->buffer) - return -1; - desc->size = desc->alloc = dst_size; - return 0; + return -1; } -static int apply_binary(struct buffer_desc *desc, struct patch *patch) +static int apply_binary(struct strbuf *buf, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; @@ -1919,7 +1833,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) /* See if the old one matches what the patch * applies to. */ - hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); + hash_sha1_file(buf->buf, buf->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) return error("the patch applies to '%s' (%s), " "which does not match the " @@ -1928,16 +1842,14 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) } else { /* Otherwise, the old one must be empty. */ - if (desc->size) + if (buf->len) return error("the patch applies to an empty " "'%s' but it is not empty", name); } get_sha1_hex(patch->new_sha1_prefix, sha1); if (is_null_sha1(sha1)) { - free(desc->buffer); - desc->alloc = desc->size = 0; - desc->buffer = NULL; + strbuf_release(buf); return 0; /* deletion patch */ } @@ -1945,43 +1857,44 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) /* We already have the postimage */ enum object_type type; unsigned long size; + char *result; - free(desc->buffer); - desc->buffer = read_sha1_file(sha1, &type, &size); - if (!desc->buffer) + result = read_sha1_file(sha1, &type, &size); + if (!result) return error("the necessary postimage %s for " "'%s' cannot be read", patch->new_sha1_prefix, name); - desc->alloc = desc->size = size; - } - else { - /* We have verified desc matches the preimage; + /* XXX read_sha1_file NUL-terminates */ + strbuf_attach(buf, result, size, size + 1); + } else { + /* We have verified buf matches the preimage; * apply the patch data to it, which is stored * in the patch->fragments->{patch,size}. */ - if (apply_binary_fragment(desc, patch)) + if (apply_binary_fragment(buf, patch)) return error("binary patch does not apply to '%s'", name); /* verify that the result matches */ - hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); + hash_sha1_file(buf->buf, buf->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); + return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", + name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } return 0; } -static int apply_fragments(struct buffer_desc *desc, struct patch *patch) +static int apply_fragments(struct strbuf *buf, struct patch *patch) { struct fragment *frag = patch->fragments; const char *name = patch->old_name ? patch->old_name : patch->new_name; if (patch->is_binary) - return apply_binary(desc, patch); + return apply_binary(buf, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) { + if (apply_one_fragment(buf, frag, patch->inaccurate_eof)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -1992,76 +1905,56 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return 0; } -static int read_file_or_gitlink(struct cache_entry *ce, char **buf_p, - unsigned long *size_p) +static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) { if (!ce) return 0; if (S_ISGITLINK(ntohl(ce->ce_mode))) { - *buf_p = xmalloc(100); - *size_p = snprintf(*buf_p, 100, - "Subproject commit %s\n", sha1_to_hex(ce->sha1)); + strbuf_grow(buf, 100); + strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1)); } else { enum object_type type; - *buf_p = read_sha1_file(ce->sha1, &type, size_p); - if (!*buf_p) + unsigned long sz; + char *result; + + result = read_sha1_file(ce->sha1, &type, &sz); + if (!result) return -1; + /* XXX read_sha1_file NUL-terminates */ + strbuf_attach(buf, result, sz, sz + 1); } return 0; } static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { - char *buf; - unsigned long size, alloc; - struct buffer_desc desc; + struct strbuf buf; - size = 0; - alloc = 0; - buf = NULL; + strbuf_init(&buf, 0); if (cached) { - if (read_file_or_gitlink(ce, &buf, &size)) + if (read_file_or_gitlink(ce, &buf)) return error("read of %s failed", patch->old_name); - alloc = size; } else if (patch->old_name) { if (S_ISGITLINK(patch->old_mode)) { - if (ce) - read_file_or_gitlink(ce, &buf, &size); - else { + if (ce) { + read_file_or_gitlink(ce, &buf); + } else { /* * There is no way to apply subproject * patch without looking at the index. */ patch->fragments = NULL; - size = 0; } - } - else { - size = xsize_t(st->st_size); - alloc = size + 8192; - buf = xmalloc(alloc); - if (read_old_data(st, patch->old_name, - &buf, &alloc, &size)) - return error("read of %s failed", - patch->old_name); + } else { + if (read_old_data(st, patch->old_name, &buf)) + return error("read of %s failed", patch->old_name); } } - desc.size = size; - desc.alloc = alloc; - desc.buffer = buf; - - if (apply_fragments(&desc, patch) < 0) + if (apply_fragments(&buf, patch) < 0) return -1; /* note with --reject this succeeds. */ - - /* NUL terminate the result */ - if (desc.alloc <= desc.size) - desc.buffer = xrealloc(desc.buffer, desc.size + 1); - desc.buffer[desc.size] = 0; - - patch->result = desc.buffer; - patch->resultsize = desc.size; + patch->result = strbuf_detach(&buf, &patch->resultsize); if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); @@ -2315,13 +2208,8 @@ static void numstat_patch_list(struct patch *patch) if (patch->is_binary) printf("-\t-\t"); else - printf("%d\t%d\t", - patch->lines_added, patch->lines_deleted); - if (line_termination && quote_c_style(name, NULL, NULL, 0)) - quote_c_style(name, NULL, stdout, 0); - else - fputs(name, stdout); - putchar(line_termination); + printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); + write_name_quoted(name, stdout, line_termination); } } @@ -2430,7 +2318,6 @@ static void remove_file(struct patch *patch, int rmdir_empty) if (update_index) { if (remove_file_from_cache(patch->old_name) < 0) die("unable to remove %s from index", patch->old_name); - cache_tree_invalidate_path(active_cache_tree, patch->old_name); } if (!cached) { if (S_ISGITLINK(patch->old_mode)) { @@ -2487,7 +2374,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) { int fd; - char *nbuf; + struct strbuf nbuf; if (S_ISGITLINK(mode)) { struct stat st; @@ -2506,23 +2393,16 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, if (fd < 0) return -1; - nbuf = convert_to_working_tree(path, buf, &size); - if (nbuf) - buf = nbuf; - - while (size) { - int written = xwrite(fd, buf, size); - if (written < 0) - die("writing file %s: %s", path, strerror(errno)); - if (!written) - die("out of space writing file %s", path); - buf += written; - size -= written; + strbuf_init(&nbuf, 0); + if (convert_to_working_tree(path, buf, size, &nbuf)) { + size = nbuf.len; + buf = nbuf.buf; } + write_or_die(fd, buf, size); + strbuf_release(&nbuf); + if (close(fd) < 0) die("closing file %s: %s", path, strerror(errno)); - if (nbuf) - free(nbuf); return 0; } @@ -2585,7 +2465,6 @@ static void create_file(struct patch *patch) mode = S_IFREG | 0644; create_one_file(path, mode, buf, size); add_index_file(path, mode, buf, size); - cache_tree_invalidate_path(active_cache_tree, path); } /* phase zero is to remove, phase one is to create */ @@ -2756,22 +2635,22 @@ static void prefix_patches(struct patch *p) static int apply_patch(int fd, const char *filename, int inaccurate_eof) { - unsigned long offset, size; - char *buffer = read_patch_file(fd, &size); + size_t offset; + struct strbuf buf; struct patch *list = NULL, **listp = &list; int skipped_patch = 0; + strbuf_init(&buf, 0); patch_input_file = filename; - if (!buffer) - return -1; + read_patch_file(&buf, fd); offset = 0; - while (size > 0) { + while (offset < buf.len) { struct patch *patch; int nr; patch = xcalloc(1, sizeof(*patch)); patch->inaccurate_eof = inaccurate_eof; - nr = parse_chunk(buffer + offset, size, patch); + nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); if (nr < 0) break; if (apply_in_reverse) @@ -2789,7 +2668,6 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) skipped_patch++; } offset += nr; - size -= nr; } if (whitespace_error && (new_whitespace == error_on_whitespace)) @@ -2824,7 +2702,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) if (summary) summary_patch_list(list); - free(buffer); + strbuf_release(&buf); return 0; } diff --git a/builtin-archive.c b/builtin-archive.c index 187491bc17..14a1b3077c 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -10,6 +10,7 @@ #include "exec_cmd.h" #include "pkt-line.h" #include "sideband.h" +#include "attr.h" static const char archive_usage[] = \ "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]"; @@ -29,7 +30,7 @@ static int run_remote_archiver(const char *remote, int argc, { char *url, buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; - pid_t pid; + struct child_process *conn; const char *exec = "git-upload-archive"; int exec_at = 0; @@ -45,9 +46,7 @@ static int run_remote_archiver(const char *remote, int argc, } url = xstrdup(remote); - pid = git_connect(fd, url, exec, 0); - if (pid < 0) - return pid; + conn = git_connect(fd, url, exec, 0); for (i = 1; i < argc; i++) { if (i == exec_at) @@ -75,11 +74,91 @@ static int run_remote_archiver(const char *remote, int argc, rv = recv_sideband("archive", fd[0], 1, 2); close(fd[0]); close(fd[1]); - rv |= finish_connect(pid); + rv |= finish_connect(conn); return !!rv; } +static void format_subst(const struct commit *commit, + const char *src, size_t len, + struct strbuf *buf) +{ + char *to_free = NULL; + struct strbuf fmt; + + if (src == buf->buf) + to_free = strbuf_detach(buf, NULL); + strbuf_init(&fmt, 0); + for (;;) { + const char *b, *c; + + b = memmem(src, len, "$Format:", 8); + if (!b || src + len < b + 9) + break; + c = memchr(b + 8, '$', len - 8); + if (!c) + break; + + strbuf_reset(&fmt); + strbuf_add(&fmt, b + 8, c - b - 8); + + strbuf_add(buf, src, b - src); + format_commit_message(commit, fmt.buf, buf); + len -= c + 1 - src; + src = c + 1; + } + strbuf_add(buf, src, len); + strbuf_release(&fmt); + free(to_free); +} + +static int convert_to_archive(const char *path, + const void *src, size_t len, + struct strbuf *buf, + const struct commit *commit) +{ + static struct git_attr *attr_export_subst; + struct git_attr_check check[1]; + + if (!commit) + return 0; + + if (!attr_export_subst) + attr_export_subst = git_attr("export-subst", 12); + + check[0].attr = attr_export_subst; + if (git_checkattr(path, ARRAY_SIZE(check), check)) + return 0; + if (!ATTR_TRUE(check[0].value)) + return 0; + + format_subst(commit, src, len, buf); + return 1; +} + +void *sha1_file_to_archive(const char *path, const unsigned char *sha1, + unsigned int mode, enum object_type *type, + unsigned long *sizep, + const struct commit *commit) +{ + void *buffer; + + buffer = read_sha1_file(sha1, type, sizep); + if (buffer && S_ISREG(mode)) { + struct strbuf buf; + size_t size = 0; + + strbuf_init(&buf, 0); + strbuf_attach(&buf, buffer, *sizep, *sizep + 1); + convert_to_working_tree(path, buf.buf, buf.len, &buf); + convert_to_archive(path, buf.buf, buf.len, &buf, commit); + buffer = strbuf_detach(&buf, &size); + *sizep = size; + } + + return buffer; +} + static int init_archiver(const char *name, struct archiver *ar) { int rv = -1, i; @@ -109,7 +188,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, const unsigned char *commit_sha1; time_t archive_time; struct tree *tree; - struct commit *commit; + const struct commit *commit; unsigned char sha1[20]; if (get_sha1(name, sha1)) @@ -142,6 +221,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, } ar_args->tree = tree; ar_args->commit_sha1 = commit_sha1; + ar_args->commit = commit; ar_args->time = archive_time; } diff --git a/builtin-blame.c b/builtin-blame.c index e17b03d876..c158d319dc 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -335,7 +335,7 @@ static struct origin *find_origin(struct scoreboard *sb, * same and diff-tree is fairly efficient about this. */ diff_setup(&diff_opts); - diff_opts.recursive = 1; + DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.detect_rename = 0; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; paths[0] = origin->path; @@ -409,7 +409,7 @@ static struct origin *find_rename(struct scoreboard *sb, const char *paths[2]; diff_setup(&diff_opts); - diff_opts.recursive = 1; + DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = origin->path; @@ -1075,7 +1075,7 @@ static int find_copy_in_parent(struct scoreboard *sb, return 1; /* nothing remains for this target */ diff_setup(&diff_opts); - diff_opts.recursive = 1; + DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; paths[0] = NULL; @@ -1093,7 +1093,7 @@ static int find_copy_in_parent(struct scoreboard *sb, if ((opt & PICKAXE_BLAME_COPY_HARDEST) || ((opt & PICKAXE_BLAME_COPY_HARDER) && (!porigin || strcmp(target->path, porigin->path)))) - diff_opts.find_copies_harder = 1; + DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); if (is_null_sha1(target->commit->object.sha1)) do_diff_cache(parent->tree->object.sha1, &diff_opts); @@ -1102,7 +1102,7 @@ static int find_copy_in_parent(struct scoreboard *sb, target->commit->tree->object.sha1, "", &diff_opts); - if (!diff_opts.find_copies_harder) + if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) diffcore_std(&diff_opts); retval = 0; @@ -1430,8 +1430,7 @@ static void get_commit_info(struct commit *commit, static void write_filename_info(const char *path) { printf("filename "); - write_name_quoted(NULL, 0, path, 1, stdout); - putchar('\n'); + write_name_quoted(path, stdout, '\n'); } /* @@ -2001,11 +2000,9 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con struct commit *commit; struct origin *origin; unsigned char head_sha1[20]; - char *buf; + struct strbuf buf; const char *ident; - int fd; time_t now; - unsigned long fin_size; int size, len; struct cache_entry *ce; unsigned mode; @@ -2023,9 +2020,11 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con origin = make_origin(commit, path); + strbuf_init(&buf, 0); if (!contents_from || strcmp("-", contents_from)) { struct stat st; const char *read_from; + unsigned long fin_size; if (contents_from) { if (stat(contents_from, &st) < 0) @@ -2038,19 +2037,16 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con read_from = path; } fin_size = xsize_t(st.st_size); - buf = xmalloc(fin_size+1); mode = canon_mode(st.st_mode); switch (st.st_mode & S_IFMT) { case S_IFREG: - fd = open(read_from, O_RDONLY); - if (fd < 0) - die("cannot open %s", read_from); - if (read_in_full(fd, buf, fin_size) != fin_size) - die("cannot read %s", read_from); + if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) + die("cannot open or read %s", read_from); break; case S_IFLNK: - if (readlink(read_from, buf, fin_size+1) != fin_size) + if (readlink(read_from, buf.buf, buf.alloc) != fin_size) die("cannot readlink %s", read_from); + buf.len = fin_size; break; default: die("unsupported file type %s", read_from); @@ -2059,26 +2055,14 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con else { /* Reading from stdin */ contents_from = "standard input"; - buf = NULL; - fin_size = 0; mode = 0; - while (1) { - ssize_t cnt = 8192; - buf = xrealloc(buf, fin_size + cnt); - cnt = xread(0, buf + fin_size, cnt); - if (cnt < 0) - die("read error %s from stdin", - strerror(errno)); - if (!cnt) - break; - fin_size += cnt; - } - buf = xrealloc(buf, fin_size + 1); + if (strbuf_read(&buf, 0, 0) < 0) + die("read error %s from stdin", strerror(errno)); } - buf[fin_size] = 0; - origin->file.ptr = buf; - origin->file.size = fin_size; - pretend_sha1_file(buf, fin_size, OBJ_BLOB, origin->blob_sha1); + convert_to_git(path, buf.buf, buf.len, &buf); + origin->file.ptr = buf.buf; + origin->file.size = buf.len; + pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); commit->util = origin; /* @@ -2311,6 +2295,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) else if (i != argc - 1) usage(blame_usage); /* garbage at end */ + setup_work_tree(); if (!has_path_in_work_tree(path)) die("cannot stat path %s: %s", path, strerror(errno)); @@ -2358,6 +2343,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * do not default to HEAD, but use the working tree * or "--contents". */ + setup_work_tree(); sb.final = fake_working_tree_commit(path, contents_from); add_pending_object(&revs, &(sb.final->object), ":"); } diff --git a/builtin-branch.c b/builtin-branch.c index c1e9a61ea5..2694c9cf49 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,9 +11,15 @@ #include "commit.h" #include "builtin.h" #include "remote.h" - -static const char builtin_branch_usage[] = - "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]"; +#include "parse-options.h" + +static const char * const builtin_branch_usage[] = { + "git-branch [options] [-r | -a]", + "git-branch [options] [-l] [-f] <branchname> [<start-point>]", + "git-branch [options] [-r] (-d | -D) <branchname>", + "git-branch [options] (-m | -M) [<oldbranch>] <newbranch>", + NULL +}; #define REF_UNKNOWN_TYPE 0x00 #define REF_LOCAL_BRANCH 0x01 @@ -142,7 +148,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!force && !in_merge_bases(rev, &head_rev, 1)) { - error("The branch '%s' is not a strict subset of " + error("The branch '%s' is not an ancestor of " "your current HEAD.\n" "If you are sure you want to delete it, " "run 'git branch -D %s'.", argv[i], argv[i]); @@ -268,23 +274,22 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } if (verbose) { - char *subject = NULL; - unsigned long subject_len = 0; + struct strbuf subject; const char *sub = " **** invalid ref ****"; + strbuf_init(&subject, 0); + commit = lookup_commit(item->sha1); if (commit && !parse_commit(commit)) { - pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - &subject, &subject_len, 0, - NULL, NULL, 0, 0); - sub = subject; + pretty_print_commit(CMIT_FMT_ONELINE, commit, + &subject, 0, NULL, NULL, 0, 0); + sub = subject.buf; } printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color), maxwidth, item->name, branch_get_color(COLOR_BRANCH_RESET), find_unique_abbrev(item->sha1, abbrev), sub); - if (subject) - free(subject); + strbuf_release(&subject); } else { printf("%c %s%s%s\n", c, branch_get_color(color), item->name, branch_get_color(COLOR_BRANCH_RESET)); @@ -502,97 +507,37 @@ static void rename_branch(const char *oldname, const char *newname, int force) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, force_delete = 0, force_create = 0; - int rename = 0, force_rename = 0; + int delete = 0, rename = 0, force_create = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; - int i; + + struct option options[] = { + OPT_GROUP("Generic options"), + OPT__VERBOSE(&verbose), + OPT_BOOLEAN( 0 , "track", &track, "set up tracking mode (see git-pull(1))"), + OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), + OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", + REF_REMOTE_BRANCH), + OPT__ABBREV(&abbrev), + + OPT_GROUP("Specific git-branch actions:"), + OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), + OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), + OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), + OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), + OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), + OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"), + OPT_END(), + }; git_config(git_branch_config); track = branch_track; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "--track")) { - track = 1; - continue; - } - if (!strcmp(arg, "--no-track")) { - track = 0; - continue; - } - if (!strcmp(arg, "-d")) { - delete = 1; - continue; - } - if (!strcmp(arg, "-D")) { - delete = 1; - force_delete = 1; - continue; - } - if (!strcmp(arg, "-f")) { - force_create = 1; - continue; - } - if (!strcmp(arg, "-m")) { - rename = 1; - continue; - } - if (!strcmp(arg, "-M")) { - rename = 1; - force_rename = 1; - continue; - } - if (!strcmp(arg, "-r")) { - kinds = REF_REMOTE_BRANCH; - continue; - } - if (!strcmp(arg, "-a")) { - kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH; - continue; - } - if (!strcmp(arg, "-l")) { - reflog = 1; - continue; - } - if (!prefixcmp(arg, "--no-abbrev")) { - abbrev = 0; - continue; - } - if (!prefixcmp(arg, "--abbrev=")) { - abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - continue; - } - if (!strcmp(arg, "-v")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--color")) { - branch_use_color = 1; - continue; - } - if (!strcmp(arg, "--no-color")) { - branch_use_color = 0; - continue; - } - usage(builtin_branch_usage); - } - - if ((delete && rename) || (delete && force_create) || - (rename && force_create)) - usage(builtin_branch_usage); + argc = parse_options(argc, argv, options, builtin_branch_usage, 0); + if (!!delete + !!rename + !!force_create > 1) + usage_with_options(builtin_branch_usage, options); head = resolve_ref("HEAD", head_sha1, 0, NULL); if (!head) @@ -600,26 +545,25 @@ int cmd_branch(int argc, const char **argv, const char *prefix) head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; - } - else { + } else { if (prefixcmp(head, "refs/heads/")) die("HEAD not found below refs/heads!"); head += 11; } if (delete) - return delete_branches(argc - i, argv + i, force_delete, kinds); - else if (i == argc) + return delete_branches(argc, argv, delete > 1, kinds); + else if (argc == 0) print_ref_list(kinds, detached, verbose, abbrev); - else if (rename && (i == argc - 1)) - rename_branch(head, argv[i], force_rename); - else if (rename && (i == argc - 2)) - rename_branch(argv[i], argv[i + 1], force_rename); - else if (i == argc - 1 || i == argc - 2) - create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head, + else if (rename && (argc == 1)) + rename_branch(head, argv[0], rename > 1); + else if (rename && (argc == 2)) + rename_branch(argv[0], argv[1], rename > 1); + else if (argc <= 2) + create_branch(argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); else - usage(builtin_branch_usage); + usage_with_options(builtin_branch_usage, options); return 0; } diff --git a/builtin-bundle.c b/builtin-bundle.c index d1840555d6..9f38e2176a 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -1,12 +1,6 @@ #include "builtin.h" #include "cache.h" -#include "object.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "list-objects.h" -#include "run-command.h" -#include "refs.h" +#include "bundle.h" /* * Basic handler for bundle files to connect repositories via sneakernet. @@ -17,361 +11,6 @@ static const char *bundle_usage="git-bundle (create <bundle> <git-rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )"; -static const char bundle_signature[] = "# v2 git bundle\n"; - -struct ref_list { - unsigned int nr, alloc; - struct ref_list_entry { - unsigned char sha1[20]; - char *name; - } *list; -}; - -static void add_to_ref_list(const unsigned char *sha1, const char *name, - struct ref_list *list) -{ - if (list->nr + 1 >= list->alloc) { - list->alloc = alloc_nr(list->nr + 1); - list->list = xrealloc(list->list, - list->alloc * sizeof(list->list[0])); - } - memcpy(list->list[list->nr].sha1, sha1, 20); - list->list[list->nr].name = xstrdup(name); - list->nr++; -} - -struct bundle_header { - struct ref_list prerequisites; - struct ref_list references; -}; - -/* returns an fd */ -static int read_header(const char *path, struct bundle_header *header) { - char buffer[1024]; - int fd; - long fpos; - FILE *ffd = fopen(path, "rb"); - - if (!ffd) - return error("could not open '%s'", path); - if (!fgets(buffer, sizeof(buffer), ffd) || - strcmp(buffer, bundle_signature)) { - fclose(ffd); - return error("'%s' does not look like a v2 bundle file", path); - } - while (fgets(buffer, sizeof(buffer), ffd) - && buffer[0] != '\n') { - int is_prereq = buffer[0] == '-'; - int offset = is_prereq ? 1 : 0; - int len = strlen(buffer); - unsigned char sha1[20]; - struct ref_list *list = is_prereq ? &header->prerequisites - : &header->references; - char delim; - - if (buffer[len - 1] == '\n') - buffer[len - 1] = '\0'; - if (get_sha1_hex(buffer + offset, sha1)) { - warning("unrecognized header: %s", buffer); - continue; - } - delim = buffer[40 + offset]; - if (!isspace(delim) && (delim != '\0' || !is_prereq)) - die ("invalid header: %s", buffer); - add_to_ref_list(sha1, isspace(delim) ? - buffer + 41 + offset : "", list); - } - fpos = ftell(ffd); - fclose(ffd); - fd = open(path, O_RDONLY); - if (fd < 0) - return error("could not open '%s'", path); - lseek(fd, fpos, SEEK_SET); - return fd; -} - -static int list_refs(struct ref_list *r, int argc, const char **argv) -{ - int i; - - for (i = 0; i < r->nr; i++) { - if (argc > 1) { - int j; - for (j = 1; j < argc; j++) - if (!strcmp(r->list[i].name, argv[j])) - break; - if (j == argc) - continue; - } - printf("%s %s\n", sha1_to_hex(r->list[i].sha1), - r->list[i].name); - } - return 0; -} - -#define PREREQ_MARK (1u<<16) - -static int verify_bundle(struct bundle_header *header, int verbose) -{ - /* - * Do fast check, then if any prereqs are missing then go line by line - * to be verbose about the errors - */ - struct ref_list *p = &header->prerequisites; - struct rev_info revs; - const char *argv[] = {NULL, "--all"}; - struct object_array refs; - struct commit *commit; - int i, ret = 0, req_nr; - const char *message = "Repository lacks these prerequisite commits:"; - - init_revisions(&revs, NULL); - for (i = 0; i < p->nr; i++) { - struct ref_list_entry *e = p->list + i; - struct object *o = parse_object(e->sha1); - if (o) { - o->flags |= PREREQ_MARK; - add_pending_object(&revs, o, e->name); - continue; - } - if (++ret == 1) - error(message); - error("%s %s", sha1_to_hex(e->sha1), e->name); - } - if (revs.pending.nr != p->nr) - return ret; - req_nr = revs.pending.nr; - setup_revisions(2, argv, &revs, NULL); - - memset(&refs, 0, sizeof(struct object_array)); - for (i = 0; i < revs.pending.nr; i++) { - struct object_array_entry *e = revs.pending.objects + i; - add_object_array(e->item, e->name, &refs); - } - - prepare_revision_walk(&revs); - - i = req_nr; - while (i && (commit = get_revision(&revs))) - if (commit->object.flags & PREREQ_MARK) - i--; - - for (i = 0; i < req_nr; i++) - if (!(refs.objects[i].item->flags & SHOWN)) { - if (++ret == 1) - error(message); - error("%s %s", sha1_to_hex(refs.objects[i].item->sha1), - refs.objects[i].name); - } - - for (i = 0; i < refs.nr; i++) - clear_commit_marks((struct commit *)refs.objects[i].item, -1); - - if (verbose) { - struct ref_list *r; - - r = &header->references; - printf("The bundle contains %d ref%s\n", - r->nr, (1 < r->nr) ? "s" : ""); - list_refs(r, 0, NULL); - r = &header->prerequisites; - printf("The bundle requires these %d ref%s\n", - r->nr, (1 < r->nr) ? "s" : ""); - list_refs(r, 0, NULL); - } - return ret; -} - -static int list_heads(struct bundle_header *header, int argc, const char **argv) -{ - return list_refs(&header->references, argc, argv); -} - -static int create_bundle(struct bundle_header *header, const char *path, - int argc, const char **argv) -{ - static struct lock_file lock; - int bundle_fd = -1; - int bundle_to_stdout; - const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); - const char **argv_pack = xmalloc(5 * sizeof(const char *)); - int i, ref_count = 0; - char buffer[1024]; - struct rev_info revs; - struct child_process rls; - FILE *rls_fout; - - bundle_to_stdout = !strcmp(path, "-"); - if (bundle_to_stdout) - bundle_fd = 1; - else - bundle_fd = hold_lock_file_for_update(&lock, path, 1); - - /* write signature */ - write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); - - /* init revs to list objects for pack-objects later */ - save_commit_buffer = 0; - init_revisions(&revs, NULL); - - /* write prerequisites */ - memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *)); - argv_boundary[0] = "rev-list"; - argv_boundary[1] = "--boundary"; - argv_boundary[2] = "--pretty=oneline"; - argv_boundary[argc + 2] = NULL; - memset(&rls, 0, sizeof(rls)); - rls.argv = argv_boundary; - rls.out = -1; - rls.git_cmd = 1; - if (start_command(&rls)) - return -1; - rls_fout = fdopen(rls.out, "r"); - while (fgets(buffer, sizeof(buffer), rls_fout)) { - unsigned char sha1[20]; - if (buffer[0] == '-') { - write_or_die(bundle_fd, buffer, strlen(buffer)); - if (!get_sha1_hex(buffer + 1, sha1)) { - struct object *object = parse_object(sha1); - object->flags |= UNINTERESTING; - add_pending_object(&revs, object, buffer); - } - } else if (!get_sha1_hex(buffer, sha1)) { - struct object *object = parse_object(sha1); - object->flags |= SHOWN; - } - } - fclose(rls_fout); - if (finish_command(&rls)) - return error("rev-list died"); - - /* write references */ - argc = setup_revisions(argc, argv, &revs, NULL); - if (argc > 1) - return error("unrecognized argument: %s'", argv[1]); - - for (i = 0; i < revs.pending.nr; i++) { - struct object_array_entry *e = revs.pending.objects + i; - unsigned char sha1[20]; - char *ref; - const char *display_ref; - int flag; - - if (e->item->flags & UNINTERESTING) - continue; - if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) - continue; - if (!resolve_ref(e->name, sha1, 1, &flag)) - flag = 0; - display_ref = (flag & REF_ISSYMREF) ? e->name : ref; - - /* - * Make sure the refs we wrote out is correct; --max-count and - * other limiting options could have prevented all the tips - * from getting output. - * - * Non commit objects such as tags and blobs do not have - * this issue as they are not affected by those extra - * constraints. - */ - if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) { - warning("ref '%s' is excluded by the rev-list options", - e->name); - free(ref); - continue; - } - /* - * If you run "git bundle create bndl v1.0..v2.0", the - * name of the positive ref is "v2.0" but that is the - * commit that is referenced by the tag, and not the tag - * itself. - */ - if (hashcmp(sha1, e->item->sha1)) { - /* - * Is this the positive end of a range expressed - * in terms of a tag (e.g. v2.0 from the range - * "v1.0..v2.0")? - */ - struct commit *one = lookup_commit_reference(sha1); - struct object *obj; - - if (e->item == &(one->object)) { - /* - * Need to include e->name as an - * independent ref to the pack-objects - * input, so that the tag is included - * in the output; otherwise we would - * end up triggering "empty bundle" - * error. - */ - obj = parse_object(sha1); - obj->flags |= SHOWN; - add_pending_object(&revs, obj, e->name); - } - free(ref); - continue; - } - - ref_count++; - write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); - write_or_die(bundle_fd, " ", 1); - write_or_die(bundle_fd, display_ref, strlen(display_ref)); - write_or_die(bundle_fd, "\n", 1); - free(ref); - } - if (!ref_count) - die ("Refusing to create empty bundle."); - - /* end header */ - write_or_die(bundle_fd, "\n", 1); - - /* write pack */ - argv_pack[0] = "pack-objects"; - argv_pack[1] = "--all-progress"; - argv_pack[2] = "--stdout"; - argv_pack[3] = "--thin"; - argv_pack[4] = NULL; - memset(&rls, 0, sizeof(rls)); - rls.argv = argv_pack; - rls.in = -1; - rls.out = bundle_fd; - rls.git_cmd = 1; - if (start_command(&rls)) - return error("Could not spawn pack-objects"); - for (i = 0; i < revs.pending.nr; i++) { - struct object *object = revs.pending.objects[i].item; - if (object->flags & UNINTERESTING) - write(rls.in, "^", 1); - write(rls.in, sha1_to_hex(object->sha1), 40); - write(rls.in, "\n", 1); - } - if (finish_command(&rls)) - return error ("pack-objects died"); - close(bundle_fd); - if (!bundle_to_stdout) - commit_lock_file(&lock); - return 0; -} - -static int unbundle(struct bundle_header *header, int bundle_fd, - int argc, const char **argv) -{ - const char *argv_index_pack[] = {"index-pack", - "--fix-thin", "--stdin", NULL}; - struct child_process ip; - - if (verify_bundle(header, 0)) - return -1; - memset(&ip, 0, sizeof(ip)); - ip.argv = argv_index_pack; - ip.in = bundle_fd; - ip.no_stdout = 1; - ip.git_cmd = 1; - if (run_command(&ip)) - return error("index-pack died"); - return list_heads(header, argc, argv); -} - int cmd_bundle(int argc, const char **argv, const char *prefix) { struct bundle_header header; @@ -395,8 +34,8 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } memset(&header, 0, sizeof(header)); - if (strcmp(cmd, "create") && - (bundle_fd = read_header(bundle_file, &header)) < 0) + if (strcmp(cmd, "create") && (bundle_fd = + read_bundle_header(bundle_file, &header)) < 0) return 1; if (!strcmp(cmd, "verify")) { @@ -408,7 +47,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } if (!strcmp(cmd, "list-heads")) { close(bundle_fd); - return !!list_heads(&header, argc, argv); + return !!list_bundle_refs(&header, argc, argv); } if (!strcmp(cmd, "create")) { if (nongit) @@ -417,7 +56,8 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } else if (!strcmp(cmd, "unbundle")) { if (nongit) die("Need a repository to unbundle."); - return !!unbundle(&header, bundle_fd, argc, argv); + return !!unbundle(&header, bundle_fd) || + list_bundle_refs(&header, argc, argv); } else usage(bundle_usage); } diff --git a/builtin-check-attr.c b/builtin-check-attr.c index d94973379c..6afdfa10a1 100644 --- a/builtin-check-attr.c +++ b/builtin-check-attr.c @@ -56,7 +56,7 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) else if (ATTR_UNSET(value)) value = "unspecified"; - write_name_quoted("", 0, argv[i], 1, stdout); + quote_c_style(argv[i], NULL, stdout, 0); printf(": %s: %s\n", argv[j+1], value); } } diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 75377b9cab..70d619da8d 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -38,7 +38,6 @@ */ #include "builtin.h" #include "cache.h" -#include "strbuf.h" #include "quote.h" #include "cache-tree.h" @@ -67,9 +66,7 @@ static void write_tempfile_record(const char *name, int prefix_length) fputs(topath[checkout_stage], stdout); putchar('\t'); - write_name_quoted("", 0, name + prefix_length, - line_termination, stdout); - putchar(line_termination); + write_name_quoted(name + prefix_length, stdout, line_termination); for (i = 0; i < 4; i++) { topath[i][0] = 0; @@ -271,28 +268,28 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) } if (read_from_stdin) { - struct strbuf buf; + struct strbuf buf, nbuf; + if (all) die("git-checkout-index: don't mix '--all' and '--stdin'"); - strbuf_init(&buf); - while (1) { - char *path_name; - const char *p; - read_line(&buf, stdin, line_termination); - if (buf.eof) - break; - if (line_termination && buf.buf[0] == '"') - path_name = unquote_c_style(buf.buf, NULL); - else - path_name = buf.buf; - p = prefix_path(prefix, prefix_length, path_name); + strbuf_init(&buf, 0); + strbuf_init(&nbuf, 0); + while (strbuf_getline(&buf, stdin, line_termination) != EOF) { + const char *p; + if (line_termination && buf.buf[0] == '"') { + strbuf_reset(&nbuf); + if (unquote_c_style(&nbuf, buf.buf, NULL)) + die("line is badly quoted"); + strbuf_swap(&buf, &nbuf); + } + p = prefix_path(prefix, prefix_length, buf.buf); checkout_file(p, prefix_length); - if (p < path_name || p > path_name + strlen(path_name)) + if (p < buf.buf || p > buf.buf + buf.len) free((char *)p); - if (path_name != buf.buf) - free(path_name); } + strbuf_release(&nbuf); + strbuf_release(&buf); } if (all) diff --git a/builtin-clean.c b/builtin-clean.c new file mode 100644 index 0000000000..56ae4eb9bb --- /dev/null +++ b/builtin-clean.c @@ -0,0 +1,153 @@ +/* + * "git clean" builtin command + * + * Copyright (C) 2007 Shawn Bohrer + * + * Based on git-clean.sh by Pavel Roskin + */ + +#include "builtin.h" +#include "cache.h" +#include "dir.h" +#include "parse-options.h" + +static int force = -1; /* unset */ + +static const char *const builtin_clean_usage[] = { + "git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...", + NULL +}; + +static int git_clean_config(const char *var, const char *value) +{ + if (!strcmp(var, "clean.requireforce")) + force = !git_config_bool(var, value); + return git_default_config(var, value); +} + +int cmd_clean(int argc, const char **argv, const char *prefix) +{ + int j; + int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; + int ignored_only = 0, baselen = 0, config_set = 0; + struct strbuf directory; + struct dir_struct dir; + const char *path, *base; + static const char **pathspec; + struct option options[] = { + OPT__QUIET(&quiet), + OPT__DRY_RUN(&show_only), + OPT_BOOLEAN('f', NULL, &force, "force"), + OPT_BOOLEAN('d', NULL, &remove_directories, + "remove whole directories"), + OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), + OPT_BOOLEAN('X', NULL, &ignored_only, + "remove only ignored files"), + OPT_END() + }; + + git_config(git_clean_config); + if (force < 0) + force = 0; + else + config_set = 1; + + argc = parse_options(argc, argv, options, builtin_clean_usage, 0); + + memset(&dir, 0, sizeof(dir)); + if (ignored_only) + dir.show_ignored = 1; + + if (ignored && ignored_only) + die("-x and -X cannot be used together"); + + if (!show_only && !force) + die("clean.requireForce%s set and -n or -f not given; " + "refusing to clean", config_set ? "" : " not"); + + dir.show_other_directories = 1; + + if (!ignored) + setup_standard_excludes(&dir); + + pathspec = get_pathspec(prefix, argv); + read_cache(); + + /* + * Calculate common prefix for the pathspec, and + * use that to optimize the directory walk + */ + baselen = common_prefix(pathspec); + path = "."; + base = ""; + if (baselen) + path = base = xmemdupz(*pathspec, baselen); + read_directory(&dir, path, base, baselen, pathspec); + strbuf_init(&directory, 0); + + for (j = 0; j < dir.nr; ++j) { + struct dir_entry *ent = dir.entries[j]; + int len, pos, specs; + struct cache_entry *ce; + struct stat st; + char *seen; + + /* + * Remove the '/' at the end that directory + * walking adds for directory entries. + */ + len = ent->len; + if (len && ent->name[len-1] == '/') + len--; + pos = cache_name_pos(ent->name, len); + if (0 <= pos) + continue; /* exact match */ + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == len && + !memcmp(ce->name, ent->name, len)) + continue; /* Yup, this one exists unmerged */ + } + + if (!lstat(ent->name, &st) && (S_ISDIR(st.st_mode))) { + int matched_path = 0; + strbuf_addstr(&directory, ent->name); + if (pathspec) { + for (specs =0; pathspec[specs]; ++specs) + /* nothing */; + seen = xcalloc(specs, 1); + /* Check if directory was explictly passed as + * pathspec. If so we want to remove it */ + if (match_pathspec(pathspec, ent->name, ent->len, + baselen, seen)) + matched_path = 1; + free(seen); + } + if (show_only && (remove_directories || matched_path)) { + printf("Would remove %s\n", directory.buf); + } else if (quiet && (remove_directories || matched_path)) { + remove_dir_recursively(&directory, 0); + } else if (remove_directories || matched_path) { + printf("Removing %s\n", directory.buf); + remove_dir_recursively(&directory, 0); + } else if (show_only) { + printf("Would not remove %s\n", directory.buf); + } else { + printf("Not removing %s\n", directory.buf); + } + strbuf_reset(&directory); + } else { + if (show_only) { + printf("Would remove %s\n", ent->name); + continue; + } else if (!quiet) { + printf("Removing %s\n", ent->name); + } + unlink(ent->name); + } + } + + strbuf_release(&directory); + return 0; +} diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index ccbcbe30da..88b0ab36eb 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -14,36 +14,6 @@ /* * FIXME! Share the code with "write-tree.c" */ -static void init_buffer(char **bufp, unsigned int *sizep) -{ - *bufp = xmalloc(BLOCKING); - *sizep = 0; -} - -static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) -{ - char one_line[2048]; - va_list args; - int len; - unsigned long alloc, size, newsize; - char *buf; - - va_start(args, fmt); - len = vsnprintf(one_line, sizeof(one_line), fmt, args); - va_end(args); - size = *sizep; - newsize = size + len + 1; - alloc = (size + 32767) & ~32767; - buf = *bufp; - if (newsize > alloc) { - alloc = (newsize + 32767) & ~32767; - buf = xrealloc(buf, alloc); - *bufp = buf; - } - *sizep = newsize - 1; - memcpy(buf + size, one_line, len); -} - static void check_valid(unsigned char *sha1, enum object_type expect) { enum object_type type = sha1_object_info(sha1, NULL); @@ -87,9 +57,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) int parents = 0; unsigned char tree_sha1[20]; unsigned char commit_sha1[20]; - char comment[1000]; - char *buffer; - unsigned int size; + struct strbuf buffer; int encoding_is_utf8; git_config(git_default_config); @@ -118,8 +86,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) /* Not having i18n.commitencoding is the same as having utf-8 */ encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); - init_buffer(&buffer, &size); - add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1)); + strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ + strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1)); /* * NOTE! This ordering means that the same exact tree merged with a @@ -127,26 +95,24 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) * if everything else stays the same. */ for (i = 0; i < parents; i++) - add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i])); + strbuf_addf(&buffer, "parent %s\n", sha1_to_hex(parent_sha1[i])); /* Person/date information */ - add_buffer(&buffer, &size, "author %s\n", git_author_info(1)); - add_buffer(&buffer, &size, "committer %s\n", git_committer_info(1)); + strbuf_addf(&buffer, "author %s\n", git_author_info(1)); + strbuf_addf(&buffer, "committer %s\n", git_committer_info(1)); if (!encoding_is_utf8) - add_buffer(&buffer, &size, - "encoding %s\n", git_commit_encoding); - add_buffer(&buffer, &size, "\n"); + strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); + strbuf_addch(&buffer, '\n'); /* And add the comment */ - while (fgets(comment, sizeof(comment), stdin) != NULL) - add_buffer(&buffer, &size, "%s", comment); + if (strbuf_read(&buffer, 0, 0) < 0) + die("git-commit-tree: read returned %s", strerror(errno)); /* And check the encoding */ - buffer[size] = '\0'; - if (encoding_is_utf8 && !is_utf8(buffer)) + if (encoding_is_utf8 && !is_utf8(buffer.buf)) fprintf(stderr, commit_utf8_warn); - if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { + if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) { printf("%s\n", sha1_to_hex(commit_sha1)); return 0; } diff --git a/builtin-config.c b/builtin-config.c index e5e243f27c..f672c9ccca 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -81,7 +81,7 @@ static int get_value(const char* key_, const char* regex_) local = repo_config = xstrdup(git_path("config")); if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); - system_wide = ETC_GITCONFIG; + system_wide = git_etc_gitconfig(); } key = xstrdup(key_); @@ -191,7 +191,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } } else if (!strcmp(argv[1], "--system")) - setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1); + setenv(CONFIG_ENVIRONMENT, git_etc_gitconfig(), 1); else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) { if (argc < 3) usage(git_config_set_usage); diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 4274ec1950..f00306fb67 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -6,8 +6,7 @@ #include "cache.h" #include "builtin.h" - -static const char count_objects_usage[] = "git-count-objects [-v]"; +#include "parse-options.h" static void count_objects(DIR *d, char *path, int len, int verbose, unsigned long *loose, @@ -67,29 +66,28 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } } -int cmd_count_objects(int ac, const char **av, const char *prefix) +static char const * const count_objects_usage[] = { + "git-count-objects [-v]", + NULL +}; + +int cmd_count_objects(int argc, const char **argv, const char *prefix) { - int i; - int verbose = 0; + int i, verbose = 0; const char *objdir = get_object_directory(); int len = strlen(objdir); char *path = xmalloc(len + 50); unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; unsigned long loose_size = 0; + struct option opts[] = { + OPT__VERBOSE(&verbose), + OPT_END(), + }; - for (i = 1; i < ac; i++) { - const char *arg = av[i]; - if (*arg != '-') - break; - else if (!strcmp(arg, "-v")) - verbose = 1; - else - usage(count_objects_usage); - } - + argc = parse_options(argc, argv, opts, count_objects_usage, 0); /* we do not take arguments other than flags for now */ - if (i < ac) - usage(count_objects_usage); + if (argc) + usage_with_options(count_objects_usage, opts); memcpy(path, objdir, len); if (len && objdir[len-1] != '/') path[len++] = '/'; diff --git a/builtin-describe.c b/builtin-describe.c index 669110cb06..6eeb9b5045 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -4,12 +4,15 @@ #include "refs.h" #include "builtin.h" #include "exec_cmd.h" +#include "parse-options.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) -static const char describe_usage[] = -"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*"; +static const char * const describe_usage[] = { + "git-describe [options] <committish>*", + NULL +}; static int debug; /* Display lots of verbose info */ static int all; /* Default to annotated tags only */ @@ -242,57 +245,42 @@ static void describe(const char *arg, int last_one) int cmd_describe(int argc, const char **argv, const char *prefix) { - int i; int contains = 0; + struct option options[] = { + OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"), + OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"), + OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"), + OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), + OPT__ABBREV(&abbrev), + OPT_INTEGER(0, "candidates", &max_candidates, + "consider <n> most recent tags (default: 10)"), + OPT_END(), + }; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg != '-') - break; - else if (!strcmp(arg, "--contains")) - contains = 1; - else if (!strcmp(arg, "--debug")) - debug = 1; - else if (!strcmp(arg, "--all")) - all = 1; - else if (!strcmp(arg, "--tags")) - tags = 1; - else if (!prefixcmp(arg, "--abbrev=")) { - abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev)) - abbrev = DEFAULT_ABBREV; - } - else if (!prefixcmp(arg, "--candidates=")) { - max_candidates = strtoul(arg + 13, NULL, 10); - if (max_candidates < 1) - max_candidates = 1; - else if (max_candidates > MAX_TAGS) - max_candidates = MAX_TAGS; - } - else - usage(describe_usage); - } + argc = parse_options(argc, argv, options, describe_usage, 0); + if (max_candidates < 1) + max_candidates = 1; + else if (max_candidates > MAX_TAGS) + max_candidates = MAX_TAGS; save_commit_buffer = 0; if (contains) { - const char **args = xmalloc((4 + argc - i) * sizeof(char*)); + const char **args = xmalloc((4 + argc) * sizeof(char*)); args[0] = "name-rev"; args[1] = "--name-only"; args[2] = "--tags"; - memcpy(args + 3, argv + i, (argc - i) * sizeof(char*)); - args[3 + argc - i] = NULL; - return cmd_name_rev(3 + argc - i, args, prefix); + memcpy(args + 3, argv, argc * sizeof(char*)); + args[3 + argc] = NULL; + return cmd_name_rev(3 + argc, args, prefix); } - if (argc <= i) + if (argc == 0) { describe("HEAD", 1); - else - while (i < argc) { - describe(argv[i], (i == argc - 1)); - i++; + } else { + while (argc-- > 0) { + describe(*argv++, argc == 0); } - + } return 0; } diff --git a/builtin-diff-files.c b/builtin-diff-files.c index 6cb30c8e12..046b7e34b5 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -31,5 +31,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; result = run_diff_files_cmd(&rev, argc, argv); - return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result; + if (DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS)) + return DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0; + return result; } diff --git a/builtin-diff-index.c b/builtin-diff-index.c index 81e7167438..556c506bfa 100644 --- a/builtin-diff-index.c +++ b/builtin-diff-index.c @@ -44,5 +44,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) return -1; } result = run_diff_index(&rev, cached); - return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result; + if (DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS)) + return DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0; + return result; } diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index 0b591c8716..2e13716eec 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -118,8 +118,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (!read_stdin) - return opt->diffopt.exit_with_status ? - opt->diffopt.has_changes: 0; + return DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS) + && DIFF_OPT_TST(&opt->diffopt, HAS_CHANGES); if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | @@ -134,5 +134,6 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) else diff_tree_stdin(line); } - return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0; + return DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS) + && DIFF_OPT_TST(&opt->diffopt, HAS_CHANGES); } diff --git a/builtin-diff.c b/builtin-diff.c index f557d21929..1b615991e1 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -35,7 +35,7 @@ static void stuff_change(struct diff_options *opt, !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode)) return; - if (opt->reverse_diff) { + if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { unsigned tmp; const unsigned char *tmp_u; const char *tmp_c; @@ -253,13 +253,13 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (diff_setup_done(&rev.diffopt) < 0) die("diff_setup_done failed"); } - rev.diffopt.allow_external = 1; - rev.diffopt.recursive = 1; + DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); + DIFF_OPT_SET(&rev.diffopt, RECURSIVE); /* If the user asked for our exit code then don't start a * pager or we would end up reporting its exit code instead. */ - if (!rev.diffopt.exit_with_status) + if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS)) setup_pager(); /* Do we have --cached and not have a pending object, then @@ -363,8 +363,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) else result = builtin_diff_combined(&rev, argc, argv, ent, ents); - if (rev.diffopt.exit_with_status) - result = rev.diffopt.has_changes; + if (DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS)) + result = DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0; if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index db133348a8..ed60847d9f 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -3,26 +3,14 @@ #include "refs.h" #include "commit.h" -#define CHUNK_SIZE 1024 - static char *get_stdin(void) { - size_t offset = 0; - char *data = xmalloc(CHUNK_SIZE); - - while (1) { - ssize_t cnt = xread(0, data + offset, CHUNK_SIZE); - if (cnt < 0) - die("error reading standard input: %s", - strerror(errno)); - if (cnt == 0) { - data[offset] = 0; - break; - } - offset += cnt; - data = xrealloc(data, offset + CHUNK_SIZE); + struct strbuf buf; + strbuf_init(&buf, 0); + if (strbuf_read(&buf, 0, 1024) < 0) { + die("error reading standard input: %s", strerror(errno)); } - return data; + return strbuf_detach(&buf, NULL); } static void show_new(enum object_type type, unsigned char *sha1_new) @@ -31,24 +19,19 @@ static void show_new(enum object_type type, unsigned char *sha1_new) find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); } -static int update_ref(const char *action, +static int update_ref_env(const char *action, const char *refname, unsigned char *sha1, unsigned char *oldval) { char msg[1024]; - char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; + const char *rla = getenv("GIT_REFLOG_ACTION"); if (!rla) rla = "(reflog update)"; - snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(refname, oldval, 0); - if (!lock) - return 1; - if (write_ref_sha1(lock, sha1, msg) < 0) - return 1; - return 0; + if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) + warning("reflog message too long: %.*s...", 50, msg); + return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); } static int update_local_ref(const char *name, @@ -78,7 +61,7 @@ static int update_local_ref(const char *name, } if (get_sha1(name, sha1_old)) { - char *msg; + const char *msg; just_store: /* new ref */ if (!strncmp(name, "refs/tags/", 10)) @@ -88,7 +71,7 @@ static int update_local_ref(const char *name, fprintf(stderr, "* %s: storing %s\n", name, note); show_new(type, sha1_new); - return update_ref(msg, name, sha1_new, NULL); + return update_ref_env(msg, name, sha1_new, NULL); } if (!hashcmp(sha1_old, sha1_new)) { @@ -102,7 +85,7 @@ static int update_local_ref(const char *name, if (!strncmp(name, "refs/tags/", 10)) { fprintf(stderr, "* %s: updating with %s\n", name, note); show_new(type, sha1_new); - return update_ref("updating tag", name, sha1_new, NULL); + return update_ref_env("updating tag", name, sha1_new, NULL); } current = lookup_commit_reference(sha1_old); @@ -117,7 +100,7 @@ static int update_local_ref(const char *name, fprintf(stderr, "* %s: fast forward to %s\n", name, note); fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref("fast forward", name, sha1_new, sha1_old); + return update_ref_env("fast forward", name, sha1_new, sha1_old); } if (!force) { fprintf(stderr, @@ -131,7 +114,7 @@ static int update_local_ref(const char *name, "* %s: forcing update to non-fast forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); - return update_ref("forced-update", name, sha1_new, sha1_old); + return update_ref_env("forced-update", name, sha1_new, sha1_old); } static int append_fetch_head(FILE *fp, @@ -239,19 +222,15 @@ static char *find_local_name(const char *remote_name, const char *refs, } if (!strncmp(remote_name, ref, len) && ref[len] == ':') { const char *local_part = ref + len + 1; - char *ret; int retlen; if (!next) retlen = strlen(local_part); else retlen = next - local_part; - ret = xmalloc(retlen + 1); - memcpy(ret, local_part, retlen); - ret[retlen] = 0; *force_p = single_force; *not_for_merge_p = not_for_merge; - return ret; + return xmemdupz(local_part, retlen); } ref = next; } @@ -456,9 +435,7 @@ static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_resu cp++; if (!*cp) break; - np = strchr(cp, '\n'); - if (!np) - np = cp + strlen(cp); + np = strchrnul(cp, '\n'); if (pass) { lrr_list[i].line = cp; lrr_list[i].name = cp + 41; @@ -482,9 +459,7 @@ static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_resu rref++; if (!*rref) break; - next = strchr(rref, '\n'); - if (!next) - next = rref + strlen(rref); + next = strchrnul(rref, '\n'); rreflen = next - rref; for (i = 0; i < lrr_count; i++) { diff --git a/fetch-pack.c b/builtin-fetch-pack.c index 9c81305be5..807fa93b53 100644 --- a/fetch-pack.c +++ b/builtin-fetch-pack.c @@ -6,19 +6,18 @@ #include "exec_cmd.h" #include "pack.h" #include "sideband.h" +#include "fetch-pack.h" +#include "run-command.h" -static int keep_pack; static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; -static int quiet; -static int verbose; -static int fetch_all; -static int depth; -static int no_progress; +static struct fetch_pack_args args = { + /* .uploadpack = */ "git-upload-pack", +}; + static const char fetch_pack_usage[] = "git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; -static const char *uploadpack = "git-upload-pack"; #define COMPLETE (1U << 0) #define COMMON (1U << 1) @@ -33,7 +32,7 @@ static const char *uploadpack = "git-upload-pack"; #define MAX_IN_VAIN 256 static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_thin_pack, use_sideband; +static int non_common_revs, multi_ack, use_sideband; static void rev_list_push(struct commit *commit, int mark) { @@ -179,8 +178,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, (multi_ack ? " multi_ack" : ""), (use_sideband == 2 ? " side-band-64k" : ""), (use_sideband == 1 ? " side-band" : ""), - (use_thin_pack ? " thin-pack" : ""), - (no_progress ? " no-progress" : ""), + (args.use_thin_pack ? " thin-pack" : ""), + (args.no_progress ? " no-progress" : ""), " ofs-delta"); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); @@ -188,13 +187,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (is_repository_shallow()) write_shallow_commits(fd[1], 1); - if (depth > 0) - packet_write(fd[1], "deepen %d", depth); + if (args.depth > 0) + packet_write(fd[1], "deepen %d", args.depth); packet_flush(fd[1]); if (!fetching) return 1; - if (depth > 0) { + if (args.depth > 0) { char line[1024]; unsigned char sha1[20]; int len; @@ -225,7 +224,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, retval = -1; while ((sha1 = get_rev())) { packet_write(fd[1], "have %s\n", sha1_to_hex(sha1)); - if (verbose) + if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; if (!(31 & ++count)) { @@ -243,7 +242,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, do { ack = get_ack(fd[0], result_sha1); - if (verbose && ack) + if (args.verbose && ack) fprintf(stderr, "got ack %d %s\n", ack, sha1_to_hex(result_sha1)); if (ack == 1) { @@ -262,7 +261,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, } while (ack); flushes--; if (got_continue && MAX_IN_VAIN < in_vain) { - if (verbose) + if (args.verbose) fprintf(stderr, "giving up\n"); break; /* give up */ } @@ -270,7 +269,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, } done: packet_write(fd[1], "done\n"); - if (verbose) + if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { multi_ack = 0; @@ -279,7 +278,7 @@ done: while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { - if (verbose) + if (args.verbose) fprintf(stderr, "got ack (%d) %s\n", ack, sha1_to_hex(result_sha1)); if (ack == 1) @@ -316,7 +315,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, static void mark_recent_complete_commits(unsigned long cutoff) { while (complete && cutoff <= complete->item->date) { - if (verbose) + if (args.verbose) fprintf(stderr, "Marking %s as complete\n", sha1_to_hex(complete->item->object.sha1)); pop_most_recent_commit(&complete, COMPLETE); @@ -331,7 +330,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) struct ref *ref, *next; struct ref *fastarray[32]; - if (nr_match && !fetch_all) { + if (nr_match && !args.fetch_all) { if (ARRAY_SIZE(fastarray) < nr_match) return_refs = xcalloc(nr_match, sizeof(struct ref *)); else { @@ -347,8 +346,8 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) if (!memcmp(ref->name, "refs/", 5) && check_ref_format(ref->name + 5)) ; /* trash */ - else if (fetch_all && - (!depth || prefixcmp(ref->name, "refs/tags/") )) { + else if (args.fetch_all && + (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { *newtail = ref; ref->next = NULL; newtail = &ref->next; @@ -364,7 +363,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) free(ref); } - if (!fetch_all) { + if (!args.fetch_all) { int i; for (i = 0; i < nr_match; i++) { ref = return_refs[i]; @@ -407,7 +406,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } } - if (!depth) { + if (!args.depth) { for_each_ref(mark_complete, NULL); if (cutoff) mark_recent_complete_commits(cutoff); @@ -441,7 +440,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) o = lookup_object(remote); if (!o || !(o->flags & COMPLETE)) { retval = 0; - if (!verbose) + if (!args.verbose) continue; fprintf(stderr, "want %s (%s)\n", sha1_to_hex(remote), @@ -450,7 +449,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } hashcpy(ref->new_sha1, local); - if (!verbose) + if (!args.verbose) continue; fprintf(stderr, "already have %s (%s)\n", sha1_to_hex(remote), @@ -459,58 +458,46 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } -static pid_t setup_sideband(int fd[2], int xd[2]) +static int sideband_demux(int fd, void *data) { - pid_t side_pid; + int *xd = data; - if (!use_sideband) { - fd[0] = xd[0]; - fd[1] = xd[1]; - return 0; - } - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through our fd[0]. - */ - if (pipe(fd) < 0) - die("fetch-pack: unable to set up pipe"); - side_pid = fork(); - if (side_pid < 0) - die("fetch-pack: unable to fork off sideband demultiplexer"); - if (!side_pid) { - /* subprocess */ - close(fd[0]); - if (xd[0] != xd[1]) - close(xd[1]); - if (recv_sideband("fetch-pack", xd[0], fd[1], 2)) - exit(1); - exit(0); - } - close(xd[0]); - close(fd[1]); - fd[1] = xd[1]; - return side_pid; + return recv_sideband("fetch-pack", xd[0], fd, 2); } -static int get_pack(int xd[2]) +static int get_pack(int xd[2], char **pack_lockfile) { - int status; - pid_t pid, side_pid; - int fd[2]; + struct async demux; const char *argv[20]; char keep_arg[256]; char hdr_arg[256]; const char **av; - int do_keep = keep_pack; - - side_pid = setup_sideband(fd, xd); + int do_keep = args.keep_pack; + struct child_process cmd; + + memset(&demux, 0, sizeof(demux)); + if (use_sideband) { + /* xd[] is talking with upload-pack; subprocess reads from + * xd[0], spits out band#2 to stderr, and feeds us band#1 + * through demux->out. + */ + demux.proc = sideband_demux; + demux.data = xd; + if (start_async(&demux)) + die("fetch-pack: unable to fork off sideband" + " demultiplexer"); + } + else + demux.out = xd[0]; + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = argv; av = argv; *hdr_arg = 0; - if (unpack_limit) { + if (!args.keep_pack && unpack_limit) { struct pack_header header; - if (read_pack_header(fd[0], &header)) + if (read_pack_header(demux.out, &header)) die("protocol error: bad pack header"); snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u", ntohl(header.hdr_version), ntohl(header.hdr_entries)); @@ -521,13 +508,15 @@ static int get_pack(int xd[2]) } if (do_keep) { + if (pack_lockfile) + cmd.out = -1; *av++ = "index-pack"; *av++ = "--stdin"; - if (!quiet && !no_progress) + if (!args.quiet && !args.no_progress) *av++ = "-v"; - if (use_thin_pack) + if (args.use_thin_pack) *av++ = "--fix-thin"; - if (keep_pack > 1 || unpack_limit) { + if (args.lock_pack || unpack_limit) { int s = sprintf(keep_arg, "--keep=fetch-pack %d on ", getpid()); if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) @@ -537,43 +526,31 @@ static int get_pack(int xd[2]) } else { *av++ = "unpack-objects"; - if (quiet) + if (args.quiet) *av++ = "-q"; } if (*hdr_arg) *av++ = hdr_arg; *av++ = NULL; - pid = fork(); - if (pid < 0) + cmd.in = demux.out; + cmd.git_cmd = 1; + if (start_command(&cmd)) die("fetch-pack: unable to fork off %s", argv[0]); - if (!pid) { - dup2(fd[0], 0); - close(fd[0]); - close(fd[1]); - execv_git_cmd(argv); - die("%s exec failed", argv[0]); - } - close(fd[0]); - close(fd[1]); - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) - die("waiting for %s: %s", argv[0], strerror(errno)); - } - if (WIFEXITED(status)) { - int code = WEXITSTATUS(status); - if (code) - die("%s died with error code %d", argv[0], code); - return 0; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - die("%s died of signal %d", argv[0], sig); - } - die("%s died of unnatural causes %d", argv[0], status); + if (do_keep && pack_lockfile) + *pack_lockfile = index_pack_lockfile(cmd.out); + + if (finish_command(&cmd)) + die("%s failed", argv[0]); + if (use_sideband && finish_async(&demux)) + die("error in sideband demultiplexer"); + return 0; } -static int fetch_pack(int fd[2], int nr_match, char **match) +static struct ref *do_fetch_pack(int fd[2], + int nr_match, + char **match, + char **pack_lockfile) { struct ref *ref; unsigned char sha1[20]; @@ -582,17 +559,17 @@ static int fetch_pack(int fd[2], int nr_match, char **match) if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); if (server_supports("multi_ack")) { - if (verbose) + if (args.verbose) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; } if (server_supports("side-band-64k")) { - if (verbose) + if (args.verbose) fprintf(stderr, "Server supports side-band-64k\n"); use_sideband = 2; } else if (server_supports("side-band")) { - if (verbose) + if (args.verbose) fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; } @@ -605,22 +582,17 @@ static int fetch_pack(int fd[2], int nr_match, char **match) goto all_done; } if (find_common(fd, sha1, ref) < 0) - if (keep_pack != 1) + if (!args.keep_pack) /* When cloning, it is not unusual to have * no common commit. */ fprintf(stderr, "warning: no common commits\n"); - if (get_pack(fd)) + if (get_pack(fd, pack_lockfile)) die("git-fetch-pack: fetch failed."); all_done: - while (ref) { - printf("%s %s\n", - sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - return 0; + return ref; } static int remove_duplicates(int nr_heads, char **heads) @@ -642,7 +614,6 @@ static int remove_duplicates(int nr_heads, char **heads) heads[dst] = heads[src]; dst++; } - heads[dst] = 0; return dst; } @@ -663,85 +634,117 @@ static int fetch_pack_config(const char *var, const char *value) static struct lock_file lock; -int main(int argc, char **argv) +static void fetch_pack_setup(void) { - int i, ret, nr_heads; - char *dest = NULL, **heads; - int fd[2]; - pid_t pid; - struct stat st; - - setup_git_directory(); + static int did_setup; + if (did_setup) + return; git_config(fetch_pack_config); - if (0 <= transfer_unpack_limit) unpack_limit = transfer_unpack_limit; else if (0 <= fetch_unpack_limit) unpack_limit = fetch_unpack_limit; + did_setup = 1; +} + +int cmd_fetch_pack(int argc, const char **argv, const char *prefix) +{ + int i, ret, nr_heads; + struct ref *ref; + char *dest = NULL, **heads; nr_heads = 0; heads = NULL; for (i = 1; i < argc; i++) { - char *arg = argv[i]; + const char *arg = argv[i]; if (*arg == '-') { if (!prefixcmp(arg, "--upload-pack=")) { - uploadpack = arg + 14; + args.uploadpack = arg + 14; continue; } if (!prefixcmp(arg, "--exec=")) { - uploadpack = arg + 7; + args.uploadpack = arg + 7; continue; } if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { - quiet = 1; + args.quiet = 1; continue; } if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - keep_pack++; - unpack_limit = 0; + args.lock_pack = args.keep_pack; + args.keep_pack = 1; continue; } if (!strcmp("--thin", arg)) { - use_thin_pack = 1; + args.use_thin_pack = 1; continue; } if (!strcmp("--all", arg)) { - fetch_all = 1; + args.fetch_all = 1; continue; } if (!strcmp("-v", arg)) { - verbose = 1; + args.verbose = 1; continue; } if (!prefixcmp(arg, "--depth=")) { - depth = strtol(arg + 8, NULL, 0); - if (stat(git_path("shallow"), &st)) - st.st_mtime = 0; + args.depth = strtol(arg + 8, NULL, 0); continue; } if (!strcmp("--no-progress", arg)) { - no_progress = 1; + args.no_progress = 1; continue; } usage(fetch_pack_usage); } - dest = arg; - heads = argv + i + 1; + dest = (char *)arg; + heads = (char **)(argv + i + 1); nr_heads = argc - i - 1; break; } if (!dest) usage(fetch_pack_usage); - pid = git_connect(fd, dest, uploadpack, verbose ? CONNECT_VERBOSE : 0); - if (pid < 0) - return 1; + + ref = fetch_pack(&args, dest, nr_heads, heads, NULL); + ret = !ref; + + while (ref) { + printf("%s %s\n", + sha1_to_hex(ref->old_sha1), ref->name); + ref = ref->next; + } + + return ret; +} + +struct ref *fetch_pack(struct fetch_pack_args *my_args, + const char *dest, + int nr_heads, + char **heads, + char **pack_lockfile) +{ + int i, ret; + int fd[2]; + struct child_process *conn; + struct ref *ref; + struct stat st; + + fetch_pack_setup(); + memcpy(&args, my_args, sizeof(args)); + if (args.depth > 0) { + if (stat(git_path("shallow"), &st)) + st.st_mtime = 0; + } + + conn = git_connect(fd, (char *)dest, args.uploadpack, + args.verbose ? CONNECT_VERBOSE : 0); if (heads && nr_heads) nr_heads = remove_duplicates(nr_heads, heads); - ret = fetch_pack(fd, nr_heads, heads); + ref = do_fetch_pack(fd, nr_heads, heads, pack_lockfile); close(fd[0]); close(fd[1]); - ret |= finish_connect(pid); + ret = finish_connect(conn); if (!ret && nr_heads) { /* If the heads to pull were given, we should have @@ -756,7 +759,7 @@ int main(int argc, char **argv) } } - if (!ret && depth > 0) { + if (!ret && args.depth > 0) { struct cache_time mtime; char *shallow = git_path("shallow"); int fd; @@ -785,5 +788,8 @@ int main(int argc, char **argv) } } - return !!ret; + if (ret) + ref = NULL; + + return ref; } diff --git a/builtin-fetch.c b/builtin-fetch.c new file mode 100644 index 0000000000..31e138eab8 --- /dev/null +++ b/builtin-fetch.c @@ -0,0 +1,662 @@ +/* + * "git fetch" + */ +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "builtin.h" +#include "path-list.h" +#include "remote.h" +#include "transport.h" +#include "run-command.h" + +static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upload-pack>] [-f | --force] [--no-tags] [-t | --tags] [-k | --keep] [-u | --update-head-ok] [--depth <depth>] [-v | --verbose] [<repository> <refspec>...]"; + +static int append, force, tags, no_tags, update_head_ok, verbose, quiet; +static const char *depth; +static char *default_rla = NULL; +static struct transport *transport; + +static void unlock_pack(void) +{ + if (transport) + transport_unlock_pack(transport); +} + +static void unlock_pack_on_signal(int signo) +{ + unlock_pack(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static void add_merge_config(struct ref **head, + const struct ref *remote_refs, + struct branch *branch, + struct ref ***tail) +{ + int i; + + for (i = 0; i < branch->merge_nr; i++) { + struct ref *rm, **old_tail = *tail; + struct refspec refspec; + + for (rm = *head; rm; rm = rm->next) { + if (branch_merge_matches(branch, i, rm->name)) { + rm->merge = 1; + break; + } + } + if (rm) + continue; + + /* + * Not fetched to a tracking branch? We need to fetch + * it anyway to allow this branch's "branch.$name.merge" + * to be honored by git-pull, but we do not have to + * fail if branch.$name.merge is misconfigured to point + * at a nonexisting branch. If we were indeed called by + * git-pull, it will notice the misconfiguration because + * there is no entry in the resulting FETCH_HEAD marked + * for merging. + */ + refspec.src = branch->merge[i]->src; + refspec.dst = NULL; + refspec.pattern = 0; + refspec.force = 0; + get_fetch_map(remote_refs, &refspec, tail, 1); + for (rm = *old_tail; rm; rm = rm->next) + rm->merge = 1; + } +} + +static struct ref *get_ref_map(struct transport *transport, + struct refspec *refs, int ref_count, int tags, + int *autotags) +{ + int i; + struct ref *rm; + struct ref *ref_map = NULL; + struct ref **tail = &ref_map; + + const struct ref *remote_refs = transport_get_remote_refs(transport); + + if (ref_count || tags) { + for (i = 0; i < ref_count; i++) { + get_fetch_map(remote_refs, &refs[i], &tail, 0); + if (refs[i].dst && refs[i].dst[0]) + *autotags = 1; + } + /* Merge everything on the command line, but not --tags */ + for (rm = ref_map; rm; rm = rm->next) + rm->merge = 1; + if (tags) { + struct refspec refspec; + refspec.src = "refs/tags/"; + refspec.dst = "refs/tags/"; + refspec.pattern = 1; + refspec.force = 0; + get_fetch_map(remote_refs, &refspec, &tail, 0); + } + } else { + /* Use the defaults */ + struct remote *remote = transport->remote; + struct branch *branch = branch_get(NULL); + int has_merge = branch_has_merge_config(branch); + if (remote && (remote->fetch_refspec_nr || has_merge)) { + for (i = 0; i < remote->fetch_refspec_nr; i++) { + get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); + if (remote->fetch[i].dst && + remote->fetch[i].dst[0]) + *autotags = 1; + if (!i && !has_merge && ref_map && + !remote->fetch[0].pattern) + ref_map->merge = 1; + } + /* + * if the remote we're fetching from is the same + * as given in branch.<name>.remote, we add the + * ref given in branch.<name>.merge, too. + */ + if (has_merge && + !strcmp(branch->remote_name, remote->name)) + add_merge_config(&ref_map, remote_refs, branch, &tail); + } else { + ref_map = get_remote_ref(remote_refs, "HEAD"); + if (!ref_map) + die("Couldn't find remote ref HEAD"); + ref_map->merge = 1; + } + } + ref_remove_duplicates(ref_map); + + return ref_map; +} + +static int s_update_ref(const char *action, + struct ref *ref, + int check_old) +{ + char msg[1024]; + char *rla = getenv("GIT_REFLOG_ACTION"); + static struct ref_lock *lock; + + if (!rla) + rla = default_rla; + snprintf(msg, sizeof(msg), "%s: %s", rla, action); + lock = lock_any_ref_for_update(ref->name, + check_old ? ref->old_sha1 : NULL, 0); + if (!lock) + return 1; + if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) + return 1; + return 0; +} + +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) +#define REFCOL_WIDTH 10 + +static int update_local_ref(struct ref *ref, + const char *remote, + int verbose, + char *display) +{ + struct commit *current = NULL, *updated; + enum object_type type; + struct branch *current_branch = branch_get(NULL); + const char *pretty_ref = ref->name + ( + !prefixcmp(ref->name, "refs/heads/") ? 11 : + !prefixcmp(ref->name, "refs/tags/") ? 10 : + !prefixcmp(ref->name, "refs/remotes/") ? 13 : + 0); + + *display = 0; + type = sha1_object_info(ref->new_sha1, NULL); + if (type < 0) + die("object %s not found", sha1_to_hex(ref->new_sha1)); + + if (!*ref->name) { + /* Not storing */ + if (verbose) + sprintf(display, "* branch %s -> FETCH_HEAD", remote); + return 0; + } + + if (!hashcmp(ref->old_sha1, ref->new_sha1)) { + if (verbose) + sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, + "[up to date]", REFCOL_WIDTH, remote, + pretty_ref); + return 0; + } + + if (current_branch && + !strcmp(ref->name, current_branch->name) && + !(update_head_ok || is_bare_repository()) && + !is_null_sha1(ref->old_sha1)) { + /* + * If this is the head, and it's not okay to update + * the head, and the old value of the head isn't empty... + */ + sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", + SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + pretty_ref); + return 1; + } + + if (!is_null_sha1(ref->old_sha1) && + !prefixcmp(ref->name, "refs/tags/")) { + sprintf(display, "- %-*s %-*s -> %s", + SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, + pretty_ref); + return s_update_ref("updating tag", ref, 0); + } + + current = lookup_commit_reference_gently(ref->old_sha1, 1); + updated = lookup_commit_reference_gently(ref->new_sha1, 1); + if (!current || !updated) { + const char *msg; + const char *what; + if (!strncmp(ref->name, "refs/tags/", 10)) { + msg = "storing tag"; + what = "[new tag]"; + } + else { + msg = "storing head"; + what = "[new branch]"; + } + + sprintf(display, "* %-*s %-*s -> %s", SUMMARY_WIDTH, what, + REFCOL_WIDTH, remote, pretty_ref); + return s_update_ref(msg, ref, 0); + } + + if (in_merge_bases(current, &updated, 1)) { + char quickref[83]; + strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcat(quickref, ".."); + strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + sprintf(display, " %-*s %-*s -> %s", SUMMARY_WIDTH, quickref, + REFCOL_WIDTH, remote, pretty_ref); + return s_update_ref("fast forward", ref, 1); + } else if (force || ref->force) { + char quickref[84]; + strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcat(quickref, "..."); + strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + sprintf(display, "+ %-*s %-*s -> %s (forced update)", + SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref); + return s_update_ref("forced-update", ref, 1); + } else { + sprintf(display, "! %-*s %-*s -> %s (non fast forward)", + SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + pretty_ref); + return 1; + } +} + +static void store_updated_refs(const char *url, struct ref *ref_map) +{ + FILE *fp; + struct commit *commit; + int url_len, i, note_len, shown_url = 0; + char note[1024]; + const char *what, *kind; + struct ref *rm; + + fp = fopen(git_path("FETCH_HEAD"), "a"); + for (rm = ref_map; rm; rm = rm->next) { + struct ref *ref = NULL; + + if (rm->peer_ref) { + ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); + strcpy(ref->name, rm->peer_ref->name); + hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); + hashcpy(ref->new_sha1, rm->old_sha1); + ref->force = rm->peer_ref->force; + } + + commit = lookup_commit_reference_gently(rm->old_sha1, 1); + if (!commit) + rm->merge = 0; + + if (!strcmp(rm->name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!prefixcmp(rm->name, "refs/heads/")) { + kind = "branch"; + what = rm->name + 11; + } + else if (!prefixcmp(rm->name, "refs/tags/")) { + kind = "tag"; + what = rm->name + 10; + } + else if (!prefixcmp(rm->name, "refs/remotes/")) { + kind = "remote branch"; + what = rm->name + 13; + } + else { + kind = ""; + what = rm->name; + } + + url_len = strlen(url); + for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) + ; + url_len = i + 1; + if (4 < i && !strncmp(".git", url + i - 3, 4)) + url_len = i - 3; + + note_len = 0; + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", + kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", url_len, url); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : + rm->old_sha1), + rm->merge ? "" : "not-for-merge", + note); + + if (ref) { + update_local_ref(ref, what, verbose, note); + if (*note) { + if (!shown_url) { + fprintf(stderr, "From %.*s\n", + url_len, url); + shown_url = 1; + } + fprintf(stderr, " %s\n", note); + } + } + } + fclose(fp); +} + +/* + * We would want to bypass the object transfer altogether if + * everything we are going to fetch already exists and connected + * locally. + * + * The refs we are going to fetch are in to_fetch (nr_heads in + * total). If running + * + * $ git-rev-list --objects to_fetch[0] to_fetch[1] ... --not --all + * + * does not error out, that means everything reachable from the + * refs we are going to fetch exists and is connected to some of + * our existing refs. + */ +static int quickfetch(struct ref *ref_map) +{ + struct child_process revlist; + struct ref *ref; + char **argv; + int i, err; + + /* + * If we are deepening a shallow clone we already have these + * objects reachable. Running rev-list here will return with + * a good (0) exit status and we'll bypass the fetch that we + * really need to perform. Claiming failure now will ensure + * we perform the network exchange to deepen our history. + */ + if (depth) + return -1; + + for (i = 0, ref = ref_map; ref; ref = ref->next) + i++; + if (!i) + return 0; + + argv = xmalloc(sizeof(*argv) * (i + 6)); + i = 0; + argv[i++] = xstrdup("rev-list"); + argv[i++] = xstrdup("--quiet"); + argv[i++] = xstrdup("--objects"); + for (ref = ref_map; ref; ref = ref->next) + argv[i++] = xstrdup(sha1_to_hex(ref->old_sha1)); + argv[i++] = xstrdup("--not"); + argv[i++] = xstrdup("--all"); + argv[i++] = NULL; + + memset(&revlist, 0, sizeof(revlist)); + revlist.argv = (const char**)argv; + revlist.git_cmd = 1; + revlist.no_stdin = 1; + revlist.no_stdout = 1; + revlist.no_stderr = 1; + err = run_command(&revlist); + + for (i = 0; argv[i]; i++) + free(argv[i]); + free(argv); + return err; +} + +static int fetch_refs(struct transport *transport, struct ref *ref_map) +{ + int ret = quickfetch(ref_map); + if (ret) + ret = transport_fetch_refs(transport, ref_map); + if (!ret) + store_updated_refs(transport->url, ref_map); + transport_unlock_pack(transport); + return ret; +} + +static int add_existing(const char *refname, const unsigned char *sha1, + int flag, void *cbdata) +{ + struct path_list *list = (struct path_list *)cbdata; + path_list_insert(refname, list); + return 0; +} + +static struct ref *find_non_local_tags(struct transport *transport, + struct ref *fetch_map) +{ + static struct path_list existing_refs = { NULL, 0, 0, 0 }; + struct path_list new_refs = { NULL, 0, 0, 1 }; + char *ref_name; + int ref_name_len; + const unsigned char *ref_sha1; + const struct ref *tag_ref; + struct ref *rm = NULL; + struct ref *ref_map = NULL; + struct ref **tail = &ref_map; + const struct ref *ref; + + for_each_ref(add_existing, &existing_refs); + for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags")) + continue; + + ref_name = xstrdup(ref->name); + ref_name_len = strlen(ref_name); + ref_sha1 = ref->old_sha1; + + if (!strcmp(ref_name + ref_name_len - 3, "^{}")) { + ref_name[ref_name_len - 3] = 0; + tag_ref = transport_get_remote_refs(transport); + while (tag_ref) { + if (!strcmp(tag_ref->name, ref_name)) { + ref_sha1 = tag_ref->old_sha1; + break; + } + tag_ref = tag_ref->next; + } + } + + if (!path_list_has_path(&existing_refs, ref_name) && + !path_list_has_path(&new_refs, ref_name) && + has_sha1_file(ref->old_sha1)) { + path_list_insert(ref_name, &new_refs); + + rm = alloc_ref(strlen(ref_name) + 1); + strcpy(rm->name, ref_name); + rm->peer_ref = alloc_ref(strlen(ref_name) + 1); + strcpy(rm->peer_ref->name, ref_name); + hashcpy(rm->old_sha1, ref_sha1); + + *tail = rm; + tail = &rm->next; + } + free(ref_name); + } + + return ref_map; +} + +static int do_fetch(struct transport *transport, + struct refspec *refs, int ref_count) +{ + struct ref *ref_map, *fetch_map; + struct ref *rm; + int autotags = (transport->remote->fetch_tags == 1); + if (transport->remote->fetch_tags == 2 && !no_tags) + tags = 1; + if (transport->remote->fetch_tags == -1) + no_tags = 1; + + if (!transport->get_refs_list || !transport->fetch) + die("Don't know how to fetch from %s", transport->url); + + /* if not appending, truncate FETCH_HEAD */ + if (!append) + fclose(fopen(git_path("FETCH_HEAD"), "w")); + + ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref) + read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); + } + + if (fetch_refs(transport, ref_map)) { + free_refs(ref_map); + return 1; + } + + fetch_map = ref_map; + + /* if neither --no-tags nor --tags was specified, do automated tag + * following ... */ + if (!(tags || no_tags) && autotags) { + ref_map = find_non_local_tags(transport, fetch_map); + if (ref_map) { + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + fetch_refs(transport, ref_map); + } + free_refs(ref_map); + } + + free_refs(fetch_map); + + return 0; +} + +static void set_option(const char *name, const char *value) +{ + int r = transport_set_option(transport, name, value); + if (r < 0) + die("Option \"%s\" value \"%s\" is not valid for %s\n", + name, value, transport->url); + if (r > 0) + warning("Option \"%s\" is ignored for %s\n", + name, transport->url); +} + +int cmd_fetch(int argc, const char **argv, const char *prefix) +{ + struct remote *remote; + int i, j, rla_offset; + static const char **refs = NULL; + int ref_nr = 0; + int cmd_len = 0; + const char *upload_pack = NULL; + int keep = 0; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + cmd_len += strlen(arg); + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--append") || !strcmp(arg, "-a")) { + append = 1; + continue; + } + if (!prefixcmp(arg, "--upload-pack=")) { + upload_pack = arg + 14; + continue; + } + if (!strcmp(arg, "--upload-pack")) { + i++; + if (i == argc) + usage(fetch_usage); + upload_pack = argv[i]; + continue; + } + if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) { + force = 1; + continue; + } + if (!strcmp(arg, "--no-tags")) { + no_tags = 1; + continue; + } + if (!strcmp(arg, "--tags") || !strcmp(arg, "-t")) { + tags = 1; + continue; + } + if (!strcmp(arg, "--keep") || !strcmp(arg, "-k")) { + keep = 1; + continue; + } + if (!strcmp(arg, "--update-head-ok") || !strcmp(arg, "-u")) { + update_head_ok = 1; + continue; + } + if (!prefixcmp(arg, "--depth=")) { + depth = arg + 8; + continue; + } + if (!strcmp(arg, "--depth")) { + i++; + if (i == argc) + usage(fetch_usage); + depth = argv[i]; + continue; + } + if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose++; + continue; + } + usage(fetch_usage); + } + + for (j = i; j < argc; j++) + cmd_len += strlen(argv[j]); + + default_rla = xmalloc(cmd_len + 5 + argc + 1); + sprintf(default_rla, "fetch"); + rla_offset = strlen(default_rla); + for (j = 1; j < argc; j++) { + sprintf(default_rla + rla_offset, " %s", argv[j]); + rla_offset += strlen(argv[j]) + 1; + } + + if (i == argc) + remote = remote_get(NULL); + else + remote = remote_get(argv[i++]); + + transport = transport_get(remote, remote->url[0]); + if (verbose >= 2) + transport->verbose = 1; + if (quiet) + transport->verbose = -1; + if (upload_pack) + set_option(TRANS_OPT_UPLOADPACK, upload_pack); + if (keep) + set_option(TRANS_OPT_KEEP, "yes"); + if (depth) + set_option(TRANS_OPT_DEPTH, depth); + + if (!transport->url) + die("Where do you want to fetch from today?"); + + if (i < argc) { + int j = 0; + refs = xcalloc(argc - i + 1, sizeof(const char *)); + while (i < argc) { + if (!strcmp(argv[i], "tag")) { + char *ref; + i++; + ref = xmalloc(strlen(argv[i]) * 2 + 22); + strcpy(ref, "refs/tags/"); + strcat(ref, argv[i]); + strcat(ref, ":refs/tags/"); + strcat(ref, argv[i]); + refs[j++] = ref; + } else + refs[j++] = argv[i]; + i++; + } + refs[j] = NULL; + ref_nr = j; + } + + signal(SIGINT, unlock_pack_on_signal); + atexit(unlock_pack); + return do_fetch(transport, parse_ref_spec(ref_nr, refs), ref_nr); +} diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index ae60fccea7..6163bd4975 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -140,12 +140,10 @@ static int handle_line(char *line) if (!strcmp(".", src) || !strcmp(src, origin)) { int len = strlen(origin); if (origin[0] == '\'' && origin[len - 1] == '\'') { - char *new_origin = xmalloc(len - 1); - memcpy(new_origin, origin + 1, len - 2); - new_origin[len - 2] = 0; - origin = new_origin; - } else + origin = xmemdupz(origin + 1, len - 2); + } else { origin = xstrdup(origin); + } } else { char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); sprintf(new_origin, "%s of %s", origin, src); @@ -178,7 +176,7 @@ static void shortlog(const char *name, unsigned char *sha1, struct commit *commit; struct object *branch; struct list subjects = { NULL, NULL, 0, 0 }; - int flags = UNINTERESTING | TREECHANGE | SEEN | SHOWN | ADDED; + int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -211,14 +209,11 @@ static void shortlog(const char *name, unsigned char *sha1, bol += 2; eol = strchr(bol, '\n'); - if (eol) { - int len = eol - bol; - oneline = xmalloc(len + 1); - memcpy(oneline, bol, len); - oneline[len] = 0; - } else + oneline = xmemdupz(bol, eol - bol); + } else { oneline = xstrdup(bol); + } append_to_list(&subjects, oneline, NULL); } diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 0327f40306..daf3a08165 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -7,6 +7,7 @@ #include "tree.h" #include "blob.h" #include "quote.h" +#include "parse-options.h" /* Quoting styles */ #define QUOTE_NONE 0 @@ -87,7 +88,6 @@ static int used_atom_cnt, sort_atom_limit, need_tagged; static int parse_atom(const char *atom, const char *ep) { const char *sp; - char *n; int i, at; sp = atom; @@ -106,7 +106,16 @@ static int parse_atom(const char *atom, const char *ep) /* Is the atom a valid one? */ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { int len = strlen(valid_atom[i].name); - if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len)) + /* + * If the atom name has a colon, strip it and everything after + * it off - it specifies the format for this entry, and + * shouldn't be used for checking against the valid_atom + * table. + */ + const char *formatp = strchr(sp, ':'); + if (!formatp || ep < formatp) + formatp = ep; + if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) break; } @@ -120,10 +129,7 @@ static int parse_atom(const char *atom, const char *ep) (sizeof *used_atom) * used_atom_cnt); used_atom_type = xrealloc(used_atom_type, (sizeof(*used_atom_type) * used_atom_cnt)); - n = xmalloc(ep - atom + 1); - memcpy(n, atom, ep - atom); - n[ep-atom] = 0; - used_atom[at] = n; + used_atom[at] = xmemdupz(atom, ep - atom); used_atom_type[at] = valid_atom[i].cmp_type; return at; } @@ -153,17 +159,18 @@ static const char *find_next(const char *cp) * Make sure the format string is well formed, and parse out * the used atoms. */ -static void verify_format(const char *format) +static int verify_format(const char *format) { const char *cp, *sp; for (cp = format; *cp && (sp = find_next(cp)); ) { const char *ep = strchr(sp, ')'); if (!ep) - die("malformatted format string %s", sp); + return error("malformatted format string %s", sp); /* sp points at "%(" and ep points at the closing ")" */ parse_atom(sp + 2, ep); cp = ep + 1; } + return 0; } /* @@ -307,54 +314,50 @@ static const char *find_wholine(const char *who, int wholen, const char *buf, un static const char *copy_line(const char *buf) { const char *eol = strchr(buf, '\n'); - char *line; - int len; if (!eol) return ""; - len = eol - buf; - line = xmalloc(len + 1); - memcpy(line, buf, len); - line[len] = 0; - return line; + return xmemdupz(buf, eol - buf); } static const char *copy_name(const char *buf) { - const char *eol = strchr(buf, '\n'); - const char *eoname = strstr(buf, " <"); - char *line; - int len; - if (!(eoname && eol && eoname < eol)) - return ""; - len = eoname - buf; - line = xmalloc(len + 1); - memcpy(line, buf, len); - line[len] = 0; - return line; + const char *cp; + for (cp = buf; *cp && *cp != '\n'; cp++) { + if (!strncmp(cp, " <", 2)) + return xmemdupz(buf, cp - buf); + } + return ""; } static const char *copy_email(const char *buf) { const char *email = strchr(buf, '<'); const char *eoemail = strchr(email, '>'); - char *line; - int len; if (!email || !eoemail) return ""; - eoemail++; - len = eoemail - email; - line = xmalloc(len + 1); - memcpy(line, email, len); - line[len] = 0; - return line; + return xmemdupz(email, eoemail + 1 - email); } -static void grab_date(const char *buf, struct atom_value *v) +static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); char *zone; unsigned long timestamp; long tz; + enum date_mode date_mode = DATE_NORMAL; + const char *formatp; + + /* + * We got here because atomname ends in "date" or "date<something>"; + * it's not possible that <something> is not ":<format>" because + * parse_atom() wouldn't have allowed it, so we can assume that no + * ":" means no format is specified, and use the default. + */ + formatp = strchr(atomname, ':'); + if (formatp != NULL) { + formatp++; + date_mode = parse_date_format(formatp); + } if (!eoemail) goto bad; @@ -364,7 +367,7 @@ static void grab_date(const char *buf, struct atom_value *v) tz = strtol(zone, NULL, 10); if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) goto bad; - v->s = xstrdup(show_date(timestamp, tz, 0)); + v->s = xstrdup(show_date(timestamp, tz, date_mode)); v->ul = timestamp; return; bad: @@ -391,7 +394,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru if (name[wholen] != 0 && strcmp(name + wholen, "name") && strcmp(name + wholen, "email") && - strcmp(name + wholen, "date")) + prefixcmp(name + wholen, "date")) continue; if (!wholine) wholine = find_wholine(who, wholen, buf, sz); @@ -403,8 +406,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru v->s = copy_name(wholine); else if (!strcmp(name + wholen, "email")) v->s = copy_email(wholine); - else if (!strcmp(name + wholen, "date")) - grab_date(wholine, v); + else if (!prefixcmp(name + wholen, "date")) + grab_date(wholine, v, name); } /* For a tag or a commit object, if "creator" or "creatordate" is @@ -424,8 +427,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru if (deref) name++; - if (!strcmp(name, "creatordate")) - grab_date(wholine, v); + if (!prefixcmp(name, "creatordate")) + grab_date(wholine, v, name); else if (!strcmp(name, "creator")) v->s = copy_line(wholine); } @@ -799,94 +802,77 @@ static struct ref_sort *default_sort(void) return sort; } -int cmd_for_each_ref(int ac, const char **av, const char *prefix) +int opt_parse_sort(const struct option *opt, const char *arg, int unset) +{ + struct ref_sort **sort_tail = opt->value; + struct ref_sort *s; + int len; + + if (!arg) /* should --no-sort void the list ? */ + return -1; + + *sort_tail = s = xcalloc(1, sizeof(*s)); + sort_tail = &s->next; + + if (*arg == '-') { + s->reverse = 1; + arg++; + } + len = strlen(arg); + s->atom = parse_atom(arg, arg+len); + return 0; +} + +static char const * const for_each_ref_usage[] = { + "git-for-each-ref [options] [<pattern>]", + NULL +}; + +int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { int i, num_refs; - const char *format = NULL; + const char *format = "%(objectname) %(objecttype)\t%(refname)"; struct ref_sort *sort = NULL, **sort_tail = &sort; - int maxcount = 0; - int quote_style = -1; /* unspecified yet */ + int maxcount = 0, quote_style = 0; struct refinfo **refs; struct grab_ref_cbdata cbdata; - for (i = 1; i < ac; i++) { - const char *arg = av[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!prefixcmp(arg, "--format=")) { - if (format) - die("more than one --format?"); - format = arg + 9; - continue; - } - if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) { - if (0 <= quote_style) - die("more than one quoting style?"); - quote_style = QUOTE_SHELL; - continue; - } - if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) { - if (0 <= quote_style) - die("more than one quoting style?"); - quote_style = QUOTE_PERL; - continue; - } - if (!strcmp(arg, "--python") ) { - if (0 <= quote_style) - die("more than one quoting style?"); - quote_style = QUOTE_PYTHON; - continue; - } - if (!strcmp(arg, "--tcl") ) { - if (0 <= quote_style) - die("more than one quoting style?"); - quote_style = QUOTE_TCL; - continue; - } - if (!prefixcmp(arg, "--count=")) { - if (maxcount) - die("more than one --count?"); - maxcount = atoi(arg + 8); - if (maxcount <= 0) - die("The number %s did not parse", arg); - continue; - } - if (!prefixcmp(arg, "--sort=")) { - struct ref_sort *s = xcalloc(1, sizeof(*s)); - int len; - - s->next = NULL; - *sort_tail = s; - sort_tail = &s->next; - - arg += 7; - if (*arg == '-') { - s->reverse = 1; - arg++; - } - len = strlen(arg); - sort->atom = parse_atom(arg, arg+len); - continue; - } - break; + struct option opts[] = { + OPT_BIT('s', "shell", "e_style, + "quote placeholders suitably for shells", QUOTE_SHELL), + OPT_BIT('p', "perl", "e_style, + "quote placeholders suitably for perl", QUOTE_PERL), + OPT_BIT(0 , "python", "e_style, + "quote placeholders suitably for python", QUOTE_PYTHON), + OPT_BIT(0 , "tcl", "e_style, + "quote placeholders suitably for tcl", QUOTE_TCL), + + OPT_GROUP(""), + OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"), + OPT_STRING( 0 , "format", &format, "format", "format to use for the output"), + OPT_CALLBACK(0 , "sort", sort_tail, "key", + "field name to sort on", &opt_parse_sort), + OPT_END(), + }; + + parse_options(argc, argv, opts, for_each_ref_usage, 0); + if (maxcount < 0) { + error("invalid --count argument: `%d'", maxcount); + usage_with_options(for_each_ref_usage, opts); } - if (quote_style < 0) - quote_style = QUOTE_NONE; + if (HAS_MULTI_BITS(quote_style)) { + error("more than one quoting style ?"); + usage_with_options(for_each_ref_usage, opts); + } + if (verify_format(format)) + usage_with_options(for_each_ref_usage, opts); if (!sort) sort = default_sort(); sort_atom_limit = used_atom_cnt; - if (!format) - format = "%(objectname) %(objecttype)\t%(refname)"; - - verify_format(format); memset(&cbdata, 0, sizeof(cbdata)); - cbdata.grab_pattern = av + i; + cbdata.grab_pattern = argv; for_each_ref(grab_single_ref, &cbdata); refs = cbdata.grab_array; num_refs = cbdata.grab_cnt; diff --git a/builtin-fsck.c b/builtin-fsck.c index 8d12287f03..e4874f64a3 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -8,6 +8,7 @@ #include "pack.h" #include "cache-tree.h" #include "tree-walk.h" +#include "parse-options.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -666,9 +667,24 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } -static const char fsck_usage[] = -"git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] " -"[--strict] [--verbose] <head-sha1>*]"; +static char const * const fsck_usage[] = { + "git-fsck [options] [<object>...]", + NULL +}; + +static struct option fsck_opts[] = { + OPT__VERBOSE(&verbose), + OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), + OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), + OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), + OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), + OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"), + OPT_BOOLEAN(0, "full", &check_full, "also consider alternate objects"), + OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), + OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, + "write dangling objects in .git/lost-found"), + OPT_END(), +}; int cmd_fsck(int argc, const char **argv, const char *prefix) { @@ -677,49 +693,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) track_object_refs = 1; errors_found = 0; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--unreachable")) { - show_unreachable = 1; - continue; - } - if (!strcmp(arg, "--tags")) { - show_tags = 1; - continue; - } - if (!strcmp(arg, "--root")) { - show_root = 1; - continue; - } - if (!strcmp(arg, "--cache")) { - keep_cache_objects = 1; - continue; - } - if (!strcmp(arg, "--no-reflogs")) { - include_reflogs = 0; - continue; - } - if (!strcmp(arg, "--full")) { - check_full = 1; - continue; - } - if (!strcmp(arg, "--strict")) { - check_strict = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--lost-found")) { - check_full = 1; - include_reflogs = 0; - write_lost_and_found = 1; - continue; - } - if (*arg == '-') - usage(fsck_usage); + argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0); + if (write_lost_and_found) { + check_full = 1; + include_reflogs = 0; } fsck_head_link(); @@ -741,22 +718,18 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) verify_pack(p, 0); for (p = packed_git; p; p = p->next) { - uint32_t i, num; + uint32_t j, num; if (open_pack_index(p)) continue; num = p->num_objects; - for (i = 0; i < num; i++) - fsck_sha1(nth_packed_object_sha1(p, i)); + for (j = 0; j < num; j++) + fsck_sha1(nth_packed_object_sha1(p, j)); } } heads = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; - - if (*arg == '-') - continue; - if (!get_sha1(arg, head_sha1)) { struct object *obj = lookup_object(head_sha1); @@ -783,7 +756,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (keep_cache_objects) { - int i; read_cache(); for (i = 0; i < active_nr; i++) { unsigned int mode; diff --git a/builtin-gc.c b/builtin-gc.c index 9397482610..799c263936 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -12,19 +12,25 @@ #include "builtin.h" #include "cache.h" +#include "parse-options.h" #include "run-command.h" #define FAILED_RUN "failed to run %s" -static const char builtin_gc_usage[] = "git-gc [--prune] [--aggressive]"; +static const char * const builtin_gc_usage[] = { + "git-gc [options]", + NULL +}; static int pack_refs = 1; static int aggressive_window = -1; +static int gc_auto_threshold = 6700; +static int gc_auto_pack_limit = 20; #define MAX_ADD 10 static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; -static const char *argv_repack[MAX_ADD] = {"repack", "-a", "-d", "-l", NULL}; +static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; static const char *argv_prune[] = {"prune", NULL}; static const char *argv_rerere[] = {"rerere", "gc", NULL}; @@ -41,6 +47,14 @@ static int gc_config(const char *var, const char *value) aggressive_window = git_config_int(var, value); return 0; } + if (!strcmp(var, "gc.auto")) { + gc_auto_threshold = git_config_int(var, value); + return 0; + } + if (!strcmp(var, "gc.autopacklimit")) { + gc_auto_pack_limit = git_config_int(var, value); + return 0; + } return git_default_config(var, value); } @@ -57,36 +71,155 @@ static void append_option(const char **cmd, const char *opt, int max_length) cmd[i] = NULL; } +static int too_many_loose_objects(void) +{ + /* + * Quickly check if a "gc" is needed, by estimating how + * many loose objects there are. Because SHA-1 is evenly + * distributed, we can check only one and get a reasonable + * estimate. + */ + char path[PATH_MAX]; + const char *objdir = get_object_directory(); + DIR *dir; + struct dirent *ent; + int auto_threshold; + int num_loose = 0; + int needed = 0; + + if (gc_auto_threshold <= 0) + return 0; + + if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { + warning("insanely long object directory %.*s", 50, objdir); + return 0; + } + dir = opendir(path); + if (!dir) + return 0; + + auto_threshold = (gc_auto_threshold + 255) / 256; + while ((ent = readdir(dir)) != NULL) { + if (strspn(ent->d_name, "0123456789abcdef") != 38 || + ent->d_name[38] != '\0') + continue; + if (++num_loose > auto_threshold) { + needed = 1; + break; + } + } + closedir(dir); + return needed; +} + +static int too_many_packs(void) +{ + struct packed_git *p; + int cnt; + + if (gc_auto_pack_limit <= 0) + return 0; + + prepare_packed_git(); + for (cnt = 0, p = packed_git; p; p = p->next) { + char path[PATH_MAX]; + size_t len; + int keep; + + if (!p->pack_local) + continue; + len = strlen(p->pack_name); + if (PATH_MAX <= len + 1) + continue; /* oops, give up */ + memcpy(path, p->pack_name, len-5); + memcpy(path + len - 5, ".keep", 6); + keep = access(p->pack_name, F_OK) && (errno == ENOENT); + if (keep) + continue; + /* + * Perhaps check the size of the pack and count only + * very small ones here? + */ + cnt++; + } + return gc_auto_pack_limit <= cnt; +} + +static int need_to_gc(void) +{ + /* + * Setting gc.auto and gc.autopacklimit to 0 or negative can + * disable the automatic gc. + */ + if (gc_auto_threshold <= 0 && gc_auto_pack_limit <= 0) + return 0; + + /* + * If there are too many loose objects, but not too many + * packs, we run "repack -d -l". If there are too many packs, + * we run "repack -A -d -l". Otherwise we tell the caller + * there is no need. + */ + if (too_many_packs()) + append_option(argv_repack, "-A", MAX_ADD); + else if (!too_many_loose_objects()) + return 0; + return 1; +} + int cmd_gc(int argc, const char **argv, const char *prefix) { - int i; int prune = 0; + int aggressive = 0; + int auto_gc = 0; char buf[80]; + struct option builtin_gc_options[] = { + OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects"), + OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), + OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), + OPT_END() + }; + git_config(gc_config); if (pack_refs < 0) pack_refs = !is_bare_repository(); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--prune")) { - prune = 1; - continue; - } - if (!strcmp(arg, "--aggressive")) { - append_option(argv_repack, "-f", MAX_ADD); - if (aggressive_window > 0) { - sprintf(buf, "--window=%d", aggressive_window); - append_option(argv_repack, buf, MAX_ADD); - } - continue; + argc = parse_options(argc, argv, builtin_gc_options, builtin_gc_usage, 0); + if (argc > 0) + usage_with_options(builtin_gc_usage, builtin_gc_options); + + if (aggressive) { + append_option(argv_repack, "-f", MAX_ADD); + if (aggressive_window > 0) { + sprintf(buf, "--window=%d", aggressive_window); + append_option(argv_repack, buf, MAX_ADD); } - /* perhaps other parameters later... */ - break; } - if (i != argc) - usage(builtin_gc_usage); + + if (auto_gc) { + /* + * Auto-gc should be least intrusive as possible. + */ + prune = 0; + if (!need_to_gc()) + return 0; + fprintf(stderr, "Packing your repository for optimum " + "performance. You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.\n"); + } else { + /* + * Use safer (for shared repos) "-A" option to + * repack when not pruning. Auto-gc makes its + * own decision. + */ + if (prune) + append_option(argv_repack, "-a", MAX_ADD); + else + append_option(argv_repack, "-A", MAX_ADD); + } if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) return error(FAILED_RUN, argv_pack_refs[0]); @@ -103,5 +236,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) return error(FAILED_RUN, argv_rerere[0]); + if (auto_gc && too_many_loose_objects()) + warning("There are too many unreachable loose objects; " + "run 'git prune' to remove them."); + return 0; } diff --git a/builtin-http-fetch.c b/builtin-http-fetch.c new file mode 100644 index 0000000000..4a50dbd95b --- /dev/null +++ b/builtin-http-fetch.c @@ -0,0 +1,77 @@ +#include "cache.h" +#include "walker.h" + +int cmd_http_fetch(int argc, const char **argv, const char *prefix) +{ + struct walker *walker; + int commits_on_stdin = 0; + int commits; + const char **write_ref = NULL; + char **commit_id; + const char *url; + int arg = 1; + int rc = 0; + int get_tree = 0; + int get_history = 0; + int get_all = 0; + int get_verbosely = 0; + int get_recover = 0; + + git_config(git_default_config); + + while (arg < argc && argv[arg][0] == '-') { + if (argv[arg][1] == 't') { + get_tree = 1; + } else if (argv[arg][1] == 'c') { + get_history = 1; + } else if (argv[arg][1] == 'a') { + get_all = 1; + get_tree = 1; + get_history = 1; + } else if (argv[arg][1] == 'v') { + get_verbosely = 1; + } else if (argv[arg][1] == 'w') { + write_ref = &argv[arg + 1]; + arg++; + } else if (!strcmp(argv[arg], "--recover")) { + get_recover = 1; + } else if (!strcmp(argv[arg], "--stdin")) { + commits_on_stdin = 1; + } + arg++; + } + if (argc < arg + 2 - commits_on_stdin) { + usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); + return 1; + } + if (commits_on_stdin) { + commits = walker_targets_stdin(&commit_id, &write_ref); + } else { + commit_id = (char **) &argv[arg++]; + commits = 1; + } + url = argv[arg]; + + walker = get_http_walker(url); + walker->get_tree = get_tree; + walker->get_history = get_history; + walker->get_all = get_all; + walker->get_verbosely = get_verbosely; + walker->get_recover = get_recover; + + rc = walker_fetch(walker, commits, commit_id, write_ref, url); + + if (commits_on_stdin) + walker_targets_free(commits, commit_id, write_ref); + + if (walker->corrupt_object_found) { + fprintf(stderr, +"Some loose object were found to be corrupt, but they might be just\n" +"a false '404 Not Found' error message sent with incorrect HTTP\n" +"status code. Suggest running git-fsck.\n"); + } + + walker_free(walker); + + return rc; +} diff --git a/builtin-init-db.c b/builtin-init-db.c index 763fa55e76..e1393b8d1e 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "builtin.h" +#include "exec_cmd.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" @@ -131,10 +132,19 @@ static void copy_templates(const char *git_dir, int len, const char *template_di int template_len; DIR *dir; - if (!template_dir) { + if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); - if (!template_dir) - template_dir = DEFAULT_GIT_TEMPLATE_DIR; + if (!template_dir) { + /* + * if the hard-coded template is relative, it is + * interpreted relative to the exec_dir + */ + template_dir = DEFAULT_GIT_TEMPLATE_DIR; + if (!is_absolute_path(template_dir)) { + const char *exec_path = git_exec_path(); + template_dir = prefix_path(exec_path, strlen(exec_path), + template_dir); + } } strcpy(template_path, template_dir); template_len = strlen(template_path); diff --git a/builtin-log.c b/builtin-log.c index 070886ae57..e1f1cf6714 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -55,13 +55,13 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; rev->verbose_header = 1; - rev->diffopt.recursive = 1; + DIFF_OPT_SET(&rev->diffopt, RECURSIVE); rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; argc = setup_revisions(argc, argv, rev, "HEAD"); if (rev->diffopt.pickaxe || rev->diffopt.filter) rev->always_show_header = 0; - if (rev->diffopt.follow_renames) { + if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { rev->always_show_header = 0; if (rev->diffopt.nr_paths != 1) usage("git logs can only follow renames on one pathname at a time"); @@ -77,11 +77,134 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, } } +/* + * This gives a rough estimate for how many commits we + * will print out in the list. + */ +static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) +{ + int n = 0; + + while (list) { + struct commit *commit = list->item; + unsigned int flags = commit->object.flags; + list = list->next; + if (!(flags & (TREESAME | UNINTERESTING))) + n++; + } + return n; +} + +static void show_early_header(struct rev_info *rev, const char *stage, int nr) +{ + if (rev->shown_one) { + rev->shown_one = 0; + if (rev->commit_format != CMIT_FMT_ONELINE) + putchar(rev->diffopt.line_termination); + } + printf("Final output: %d %s\n", nr, stage); +} + +struct itimerval early_output_timer; + +static void log_show_early(struct rev_info *revs, struct commit_list *list) +{ + int i = revs->early_output; + int show_header = 1; + + sort_in_topological_order(&list, revs->lifo); + while (list && i) { + struct commit *commit = list->item; + switch (simplify_commit(revs, commit)) { + case commit_show: + if (show_header) { + int n = estimate_commit_count(revs, list); + show_early_header(revs, "incomplete", n); + show_header = 0; + } + log_tree_commit(revs, commit); + i--; + break; + case commit_ignore: + break; + case commit_error: + return; + } + list = list->next; + } + + /* Did we already get enough commits for the early output? */ + if (!i) + return; + + /* + * ..if no, then repeat it twice a second until we + * do. + * + * NOTE! We don't use "it_interval", because if the + * reader isn't listening, we want our output to be + * throttled by the writing, and not have the timer + * trigger every second even if we're blocked on a + * reader! + */ + early_output_timer.it_value.tv_sec = 0; + early_output_timer.it_value.tv_usec = 500000; + setitimer(ITIMER_REAL, &early_output_timer, NULL); +} + +static void early_output(int signal) +{ + show_early_output = log_show_early; +} + +static void setup_early_output(struct rev_info *rev) +{ + struct sigaction sa; + + /* + * Set up the signal handler, minimally intrusively: + * we only set a single volatile integer word (not + * using sigatomic_t - trying to avoid unnecessary + * system dependencies and headers), and using + * SA_RESTART. + */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = early_output; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + /* + * If we can get the whole output in less than a + * tenth of a second, don't even bother doing the + * early-output thing.. + * + * This is a one-time-only trigger. + */ + early_output_timer.it_value.tv_sec = 0; + early_output_timer.it_value.tv_usec = 100000; + setitimer(ITIMER_REAL, &early_output_timer, NULL); +} + +static void finish_early_output(struct rev_info *rev) +{ + int n = estimate_commit_count(rev, rev->commits); + signal(SIGALRM, SIG_IGN); + show_early_header(rev, "done", n); +} + static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + if (rev->early_output) + setup_early_output(rev); + prepare_revision_walk(rev); + + if (rev->early_output) + finish_early_output(rev); + while ((commit = get_revision(rev)) != NULL) { log_tree_commit(rev, commit); if (!rev->reflog_info) { @@ -185,11 +308,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct tag *t = (struct tag *)o; printf("%stag %s%s\n\n", - diff_get_color(rev.diffopt.color_diff, - DIFF_COMMIT), + diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), t->tag, - diff_get_color(rev.diffopt.color_diff, - DIFF_RESET)); + diff_get_color_opt(&rev.diffopt, DIFF_RESET)); ret = show_object(o->sha1, 1); objects[i].item = (struct object *)t->tagged; i--; @@ -197,11 +318,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) } case OBJ_TREE: printf("%stree %s%s\n\n", - diff_get_color(rev.diffopt.color_diff, - DIFF_COMMIT), + diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, - diff_get_color(rev.diffopt.color_diff, - DIFF_RESET)); + diff_get_color_opt(&rev.diffopt, DIFF_RESET)); read_tree_recursive((struct tree *)o, "", 0, 0, NULL, show_tree_object); break; @@ -273,6 +392,8 @@ static int istitlechar(char c) static char *extra_headers = NULL; static int extra_headers_size = 0; static const char *fmt_patch_suffix = ".patch"; +static int numbered = 0; +static int auto_number = 0; static int git_format_config(const char *var, const char *value) { @@ -297,6 +418,15 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { return 0; } + if (!strcmp(var, "format.numbered")) { + if (!strcasecmp(value, "auto")) { + auto_number = 1; + return 0; + } + + numbered = git_config_bool(var, value); + return 0; + } return git_log_config(var, value); } @@ -441,8 +571,6 @@ static const char *clean_message_id(const char *msg_id) { char ch; const char *a, *z, *m; - char *n; - size_t len; m = msg_id; while ((ch = *m) && (isspace(ch) || (ch == '<'))) @@ -458,11 +586,7 @@ static const char *clean_message_id(const char *msg_id) die("insane in-reply-to: %s", msg_id); if (++z == m) return a; - len = z - a; - n = xmalloc(len + 1); - memcpy(n, a, len); - n[len] = 0; - return n; + return xmemdupz(a, z - a); } int cmd_format_patch(int argc, const char **argv, const char *prefix) @@ -472,7 +596,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct rev_info rev; int nr = 0, total, i, j; int use_stdout = 0; - int numbered = 0; int start_number = -1; int keep_subject = 0; int numbered_files = 0; /* _just_ numbers */ @@ -493,7 +616,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.combine_merges = 0; rev.ignore_merges = 1; rev.diffopt.msg_sep = ""; - rev.diffopt.recursive = 1; + DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; rev.extra_headers = extra_headers; @@ -509,6 +632,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--numbered")) numbered = 1; + else if (!strcmp(argv[i], "-N") || + !strcmp(argv[i], "--no-numbered")) { + numbered = 0; + auto_number = 0; + } else if (!prefixcmp(argv[i], "--start-number=")) start_number = strtol(argv[i] + 15, NULL, 10); else if (!strcmp(argv[i], "--numbered-files")) @@ -541,9 +669,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) endpos = strchr(committer, '>'); if (!endpos) die("bogos committer info %s\n", committer); - add_signoff = xmalloc(endpos - committer + 2); - memcpy(add_signoff, committer, endpos - committer + 1); - add_signoff[endpos - committer + 1] = 0; + add_signoff = xmemdupz(committer, endpos - committer + 1); } else if (!strcmp(argv[i], "--attach")) { rev.mime_boundary = git_version_string; @@ -598,8 +724,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; - if (!rev.diffopt.text) - rev.diffopt.binary = 1; + if (!DIFF_OPT_TST(&rev.diffopt, TEXT)) + DIFF_OPT_SET(&rev.diffopt, BINARY); if (!output_directory && !use_stdout) output_directory = prefix; @@ -650,6 +776,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) list[nr - 1] = commit; } total = nr; + if (!keep_subject && auto_number && total > 1) + numbered = 1; if (numbered) rev.total = total + start_number - 1; rev.add_signoff = add_signoff; @@ -755,7 +883,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) revs.diff = 1; revs.combine_merges = 0; revs.ignore_merges = 1; - revs.diffopt.recursive = 1; + DIFF_OPT_SET(&revs.diffopt, RECURSIVE); if (add_pending_commit(head, &revs, 0)) die("Unknown commit %s", head); @@ -792,13 +920,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) sign = '-'; if (verbose) { - char *buf = NULL; - unsigned long buflen = 0; - pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - &buf, &buflen, 0, NULL, NULL, 0, 0); + struct strbuf buf; + strbuf_init(&buf, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, + &buf, 0, NULL, NULL, 0, 0); printf("%c %s %s\n", sign, - sha1_to_hex(commit->object.sha1), buf); - free(buf); + sha1_to_hex(commit->object.sha1), buf.buf); + strbuf_release(&buf); } else { printf("%c %s\n", sign, diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 171d449048..7f60709830 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -84,8 +84,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); - write_name_quoted("", 0, ent->name + offset, line_terminator, stdout); - putchar(line_terminator); + write_name_quoted(ent->name + offset, stdout, line_terminator); } static void show_other_files(struct dir_struct *dir) @@ -208,21 +207,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (!show_stage) { fputs(tag, stdout); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); - } - else { + } else { printf("%s%06o %s %d\t", tag, ntohl(ce->ce_mode), abbrev ? find_unique_abbrev(ce->sha1,abbrev) : sha1_to_hex(ce->sha1), ce_stage(ce)); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); } + write_name_quoted(ce->name + offset, stdout, line_terminator); } static void show_files(struct dir_struct *dir, const char *prefix) @@ -300,7 +293,6 @@ static void prune_cache(const char *prefix) static const char *verify_pathspec(const char *prefix) { const char **p, *n, *prev; - char *real_prefix; unsigned long max; prev = NULL; @@ -327,14 +319,8 @@ static const char *verify_pathspec(const char *prefix) if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) die("git-ls-files: cannot generate relative filenames containing '..'"); - real_prefix = NULL; prefix_len = max; - if (max) { - real_prefix = xmalloc(max + 1); - memcpy(real_prefix, prev, max); - real_prefix[max] = 0; - } - return real_prefix; + return max ? xmemdupz(prev, max) : NULL; } /* @@ -401,8 +387,8 @@ static void overlay_tree(const char *tree_name, const char *prefix) static const char ls_files_usage[] = "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] " - "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] " - "[--] [<file>]*"; + "[ --exclude-per-directory=<filename> ] [--exclude-standard] " + "[--full-name] [--abbrev] [--] [<file>]*"; int cmd_ls_files(int argc, const char **argv, const char *prefix) { @@ -510,6 +496,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) dir.exclude_per_dir = arg + 24; continue; } + if (!strcmp(arg, "--exclude-standard")) { + exc_given = 1; + setup_standard_excludes(&dir); + continue; + } if (!strcmp(arg, "--full-name")) { prefix_offset = 0; continue; @@ -539,11 +530,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) break; } - if (require_work_tree && !is_inside_work_tree()) { - const char *work_tree = get_git_work_tree(); - if (!work_tree || chdir(work_tree)) - die("This operation must be run in a work tree"); - } + if (require_work_tree && !is_inside_work_tree()) + setup_work_tree(); pathspec = get_pathspec(prefix, argv + i); diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c new file mode 100644 index 0000000000..56f3f88023 --- /dev/null +++ b/builtin-ls-remote.c @@ -0,0 +1,74 @@ +#include "builtin.h" +#include "cache.h" +#include "transport.h" +#include "remote.h" + +static const char ls_remote_usage[] = +"git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>"; + +int cmd_ls_remote(int argc, const char **argv, const char *prefix) +{ + int i; + const char *dest = NULL; + int nongit = 0; + unsigned flags = 0; + const char *uploadpack = NULL; + + struct remote *remote; + struct transport *transport; + const struct ref *ref; + + setup_git_directory_gently(&nongit); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (*arg == '-') { + if (!prefixcmp(arg, "--upload-pack=")) { + uploadpack = arg + 14; + continue; + } + if (!prefixcmp(arg, "--exec=")) { + uploadpack = arg + 7; + continue; + } + if (!strcmp("--tags", arg)) { + flags |= REF_TAGS; + continue; + } + if (!strcmp("--heads", arg)) { + flags |= REF_HEADS; + continue; + } + if (!strcmp("--refs", arg)) { + flags |= REF_NORMAL; + continue; + } + usage(ls_remote_usage); + } + dest = arg; + break; + } + + if (!dest || i != argc - 1) + usage(ls_remote_usage); + + remote = nongit ? NULL : remote_get(dest); + if (remote && !remote->url_nr) + die("remote %s has no configured URL", dest); + transport = transport_get(remote, remote ? remote->url[0] : dest); + if (uploadpack != NULL) + transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); + + ref = transport_get_remote_refs(transport); + + if (!ref) + return 1; + + while (ref) { + if (check_ref_type(ref, flags)) + printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + ref = ref->next; + } + return 0; +} diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index cb4be4fabb..7abe333ce9 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -112,10 +112,8 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, abbrev ? find_unique_abbrev(sha1, abbrev) : sha1_to_hex(sha1)); } - write_name_quoted(base + chomp_prefix, baselen - chomp_prefix, - pathname, - line_termination, stdout); - putchar(line_termination); + write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix, + pathname, stdout, line_termination); return retval; } diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index d7cb11dc0d..2600847974 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -288,7 +288,7 @@ static void cleanup_space(char *buf) } static void decode_header(char *it, unsigned itsize); -static char *header[MAX_HDR_PARSED] = { +static const char *header[MAX_HDR_PARSED] = { "From","Subject","Date", }; @@ -915,6 +915,7 @@ static void handle_info(void) static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch) { + int peek; keep_subject = ks; metainfo_charset = encoding; fin = in; @@ -935,6 +936,11 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); + do { + peek = fgetc(in); + } while (isspace(peek)); + ungetc(peek, in); + /* process the email header */ while (read_one_header_line(line, sizeof(line), fin)) check_header(line, sizeof(line), p_hdr_data, 1); diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index 10fa177340..46b27cdaea 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -170,6 +170,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, { char name[PATH_MAX]; int ret = -1; + int peek; FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); int file_done = 0; @@ -179,6 +180,11 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, goto out; } + do { + peek = fgetc(f); + } while (isspace(peek)); + ungetc(peek, f); + if (fgets(buf, sizeof(buf), f) == NULL) { /* empty stdin is OK */ if (f != stdin) { diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c new file mode 100644 index 0000000000..8f5bbaf402 --- /dev/null +++ b/builtin-merge-ours.c @@ -0,0 +1,28 @@ +/* + * Implementation of git-merge-ours.sh as builtin + * + * Copyright (c) 2007 Thomas Harning Jr + * Original: + * Original Copyright (c) 2005 Junio C Hamano + * + * Pretend we resolved the heads, but declare our tree trumps everybody else. + */ +#include "git-compat-util.h" +#include "builtin.h" + +static const char *diff_index_args[] = { + "diff-index", "--quiet", "--cached", "HEAD", "--", NULL +}; +#define NARGS (ARRAY_SIZE(diff_index_args) - 1) + +int cmd_merge_ours(int argc, const char **argv, const char *prefix) +{ + /* + * We need to exit with 2 if the index does not match our HEAD tree, + * because the current index is what we will be committing as the + * merge result. + */ + if (cmd_diff_index(NARGS, diff_index_args, prefix)) + exit(2); + exit(0); +} diff --git a/builtin-mv.c b/builtin-mv.c index 3563216aca..a3f9ad1744 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -8,9 +8,12 @@ #include "dir.h" #include "cache-tree.h" #include "path-list.h" +#include "parse-options.h" -static const char builtin_mv_usage[] = -"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)"; +static const char * const builtin_mv_usage[] = { + "git-mv [options] <source>... <destination>", + NULL +}; static const char **copy_pathspec(const char *prefix, const char **pathspec, int count, int base_name) @@ -22,10 +25,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, for (i = 0; i < count; i++) { int length = strlen(result[i]); if (length > 0 && result[i][length - 1] == '/') { - char *without_slash = xmalloc(length); - memcpy(without_slash, result[i], length - 1); - without_slash[length - 1] = '\0'; - result[i] = without_slash; + result[i] = xmemdupz(result[i], length - 1); } if (base_name) { const char *last_slash = strrchr(result[i], '/'); @@ -64,8 +64,14 @@ static struct lock_file lock_file; int cmd_mv(int argc, const char **argv, const char *prefix) { - int i, newfd, count; + int i, newfd; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + struct option builtin_mv_options[] = { + OPT__DRY_RUN(&show_only), + OPT_BOOLEAN('f', NULL, &force, "force move/rename even if target exists"), + OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), + OPT_END(), + }; const char **source, **destination, **dest_path; enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; struct stat st; @@ -81,52 +87,29 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die("index file corrupt"); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, builtin_mv_options, builtin_mv_usage, 0); + if (--argc < 1) + usage_with_options(builtin_mv_usage, builtin_mv_options); - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-n")) { - show_only = 1; - continue; - } - if (!strcmp(arg, "-f")) { - force = 1; - continue; - } - if (!strcmp(arg, "-k")) { - ignore_errors = 1; - continue; - } - usage(builtin_mv_usage); - } - count = argc - i - 1; - if (count < 1) - usage(builtin_mv_usage); - - source = copy_pathspec(prefix, argv + i, count, 0); - modes = xcalloc(count, sizeof(enum update_mode)); - dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0); + source = copy_pathspec(prefix, argv, argc, 0); + modes = xcalloc(argc, sizeof(enum update_mode)); + dest_path = copy_pathspec(prefix, argv + argc, 1, 0); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = copy_pathspec(dest_path[0], argv + i, count, 1); + destination = copy_pathspec(dest_path[0], argv, argc, 1); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = copy_pathspec(dest_path[0], argv + i, count, 1); + destination = copy_pathspec(dest_path[0], argv, argc, 1); } else { - if (count != 1) - usage(builtin_mv_usage); + if (argc != 1) + usage_with_options(builtin_mv_usage, builtin_mv_options); destination = dest_path; } /* Checking */ - for (i = 0; i < count; i++) { + for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; int length, src_is_dir; const char *bad = NULL; @@ -170,13 +153,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (last - first > 0) { source = xrealloc(source, - (count + last - first) + (argc + last - first) * sizeof(char *)); destination = xrealloc(destination, - (count + last - first) + (argc + last - first) * sizeof(char *)); modes = xrealloc(modes, - (count + last - first) + (argc + last - first) * sizeof(enum update_mode)); } @@ -186,13 +169,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) for (j = 0; j < last - first; j++) { const char *path = active_cache[first + j]->name; - source[count + j] = path; - destination[count + j] = + source[argc + j] = path; + destination[argc + j] = prefix_path(dst, dst_len, path + length); - modes[count + j] = INDEX; + modes[argc + j] = INDEX; } - count += last - first; + argc += last - first; } } else if (lstat(dst, &st) == 0) { bad = "destination exists"; @@ -219,12 +202,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (bad) { if (ignore_errors) { - if (--count > 0) { + if (--argc > 0) { memmove(source + i, source + i + 1, - (count - i) * sizeof(char *)); + (argc - i) * sizeof(char *)); memmove(destination + i, destination + i + 1, - (count - i) * sizeof(char *)); + (argc - i) * sizeof(char *)); } } else die ("%s, source=%s, destination=%s", @@ -232,7 +215,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } } - for (i = 0; i < count; i++) { + for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; if (show_only || verbose) @@ -256,7 +239,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) path_list_insert(dst, &added); } - if (show_only) { + if (show_only) { show_list("Changed : ", &changed); show_list("Adding : ", &added); show_list("Deleting : ", &deleted); @@ -276,11 +259,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) add_file_to_cache(path, verbose); } - for (i = 0; i < deleted.nr; i++) { - const char *path = deleted.items[i].path; - remove_file_from_cache(path); - cache_tree_invalidate_path(active_cache_tree, path); - } + for (i = 0; i < deleted.nr; i++) + remove_file_from_cache(deleted.items[i].path); if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 03083e9477..a0c89a827b 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -3,12 +3,10 @@ #include "commit.h" #include "tag.h" #include "refs.h" +#include "parse-options.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ -static const char name_rev_usage[] = - "git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n"; - typedef struct rev_name { const char *tip_name; int generation; @@ -153,51 +151,41 @@ static const char* get_rev_name(struct object *o) } } +static char const * const name_rev_usage[] = { + "git-name-rev [options] ( --all | --stdin | <commit>... )", + NULL +}; + int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; - int as_is = 0, all = 0, transform_stdin = 0; + int all = 0, transform_stdin = 0; struct name_ref_data data = { 0, 0, NULL }; + struct option opts[] = { + OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), + OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"), + OPT_STRING(0, "refs", &data.ref_filter, "pattern", + "only use refs matching <pattern>"), + OPT_GROUP(""), + OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), + OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), + OPT_END(), + }; git_config(git_default_config); + argc = parse_options(argc, argv, opts, name_rev_usage, 0); + if (!!all + !!transform_stdin + !!argc > 1) { + error("Specify either a list, or --all, not both!"); + usage_with_options(name_rev_usage, opts); + } + if (all || transform_stdin) + cutoff = 0; - if (argc < 2) - usage(name_rev_usage); - - for (--argc, ++argv; argc; --argc, ++argv) { + for (; argc; argc--, argv++) { unsigned char sha1[20]; struct object *o; struct commit *commit; - if (!as_is && (*argv)[0] == '-') { - if (!strcmp(*argv, "--")) { - as_is = 1; - continue; - } else if (!strcmp(*argv, "--name-only")) { - data.name_only = 1; - continue; - } else if (!strcmp(*argv, "--tags")) { - data.tags_only = 1; - continue; - } else if (!prefixcmp(*argv, "--refs=")) { - data.ref_filter = *argv + 7; - continue; - } else if (!strcmp(*argv, "--all")) { - if (argc > 1) - die("Specify either a list, or --all, not both!"); - all = 1; - cutoff = 0; - continue; - } else if (!strcmp(*argv, "--stdin")) { - if (argc > 1) - die("Specify either a list, or --stdin, not both!"); - transform_stdin = 1; - cutoff = 0; - continue; - } - usage(name_rev_usage); - } - if (get_sha1(*argv, sha1)) { fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", *argv); @@ -212,10 +200,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) } commit = (struct commit *)o; - if (cutoff > commit->date) cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 228040486e..4f446588ac 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,13 +15,17 @@ #include "list-objects.h" #include "progress.h" +#ifdef THREADED_DELTA_SEARCH +#include <pthread.h> +#endif + static const char pack_usage[] = "\ git-pack-objects [{ -q | --progress | --all-progress }] \n\ [--max-pack-size=N] [--local] [--incremental] \n\ [--window=N] [--window-memory=N] [--depth=N] \n\ [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\ - [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ - [--stdout | base-name] [<ref-list | <object-list]"; + [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ + [--stdout | base-name] [--keep-unreachable] [<ref-list | <object-list]"; struct object_entry { struct pack_idx_entry idx; @@ -53,24 +57,23 @@ struct object_entry { * nice "minimum seek" order. */ static struct object_entry *objects; -static struct object_entry **written_list; +static struct pack_idx_entry **written_list; static uint32_t nr_objects, nr_alloc, nr_result, nr_written; static int non_empty; -static int no_reuse_delta, no_reuse_object; +static int no_reuse_delta, no_reuse_object, keep_unreachable; static int local; static int incremental; static int allow_ofs_delta; -static const char *pack_tmp_name, *idx_tmp_name; -static char tmpname[PATH_MAX]; static const char *base_name; static int progress = 1; static int window = 10; static uint32_t pack_size_limit; static int depth = 50; +static int delta_search_threads = 1; static int pack_to_stdout; static int num_preferred_base; -static struct progress progress_state; +static struct progress *progress_state; static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; @@ -78,7 +81,6 @@ static unsigned long delta_cache_size = 0; static unsigned long max_delta_cache_size = 0; static unsigned long cache_max_small_delta_size = 1000; -static unsigned long window_memory_usage = 0; static unsigned long window_memory_limit = 0; /* @@ -575,7 +577,7 @@ static off_t write_one(struct sha1file *f, e->idx.offset = 0; return 0; } - written_list[nr_written++] = e; + written_list[nr_written++] = &e->idx; /* make sure off_t is sufficiently large not to wrap */ if (offset > offset + size) @@ -583,12 +585,6 @@ static off_t write_one(struct sha1file *f, return offset + size; } -static int open_object_dir_tmp(const char *path) -{ - snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path); - return xmkstemp(tmpname); -} - /* forward declaration for write_pack_file */ static int adjust_perm(const char *path, mode_t mode); @@ -602,16 +598,21 @@ static void write_pack_file(void) uint32_t nr_remaining = nr_result; if (do_progress) - start_progress(&progress_state, "Writing %u objects...", "", nr_result); - written_list = xmalloc(nr_objects * sizeof(struct object_entry *)); + progress_state = start_progress("Writing objects", nr_result); + written_list = xmalloc(nr_objects * sizeof(*written_list)); do { unsigned char sha1[20]; + char *pack_tmp_name = NULL; if (pack_to_stdout) { - f = sha1fd(1, "<stdout>"); + f = sha1fd_throughput(1, "<stdout>", progress_state); } else { - int fd = open_object_dir_tmp("tmp_pack_XXXXXX"); + char tmpname[PATH_MAX]; + int fd; + snprintf(tmpname, sizeof(tmpname), + "%s/tmp_pack_XXXXXX", get_object_directory()); + fd = xmkstemp(tmpname); pack_tmp_name = xstrdup(tmpname); f = sha1fd(fd, pack_tmp_name); } @@ -628,8 +629,7 @@ static void write_pack_file(void) if (!offset_one) break; offset = offset_one; - if (do_progress) - display_progress(&progress_state, written); + display_progress(progress_state, written); } /* @@ -639,19 +639,20 @@ static void write_pack_file(void) if (pack_to_stdout || nr_written == nr_remaining) { sha1close(f, sha1, 1); } else { - sha1close(f, sha1, 0); - fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written); - close(f->fd); + int fd = sha1close(f, NULL, 0); + fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written); + close(fd); } if (!pack_to_stdout) { mode_t mode = umask(0); + char *idx_tmp_name, tmpname[PATH_MAX]; umask(mode); mode = 0444 & ~mode; - idx_tmp_name = write_idx_file(NULL, - (struct pack_idx_entry **) written_list, nr_written, sha1); + idx_tmp_name = write_idx_file(NULL, written_list, + nr_written, sha1); snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); if (adjust_perm(pack_tmp_name, mode)) @@ -668,19 +669,20 @@ static void write_pack_file(void) if (rename(idx_tmp_name, tmpname)) die("unable to rename temporary index file: %s", strerror(errno)); + free(idx_tmp_name); + free(pack_tmp_name); puts(sha1_to_hex(sha1)); } /* mark written objects as written to previous pack */ for (j = 0; j < nr_written; j++) { - written_list[j]->idx.offset = (off_t)-1; + written_list[j]->offset = (off_t)-1; } nr_remaining -= nr_written; } while (nr_remaining && i < nr_objects); free(written_list); - if (do_progress) - stop_progress(&progress_state); + stop_progress(&progress_state); if (written != nr_result) die("wrote %u objects while expecting %u", written, nr_result); /* @@ -848,8 +850,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, else object_ix[-1 - ix] = nr_objects; - if (progress) - display_progress(&progress_state, nr_objects); + display_progress(progress_state, nr_objects); if (name && no_try_delta(name)) entry->no_try_delta = 1; @@ -1291,6 +1292,31 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, return 0; } +#ifdef THREADED_DELTA_SEARCH + +static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER; +#define read_lock() pthread_mutex_lock(&read_mutex) +#define read_unlock() pthread_mutex_unlock(&read_mutex) + +static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER; +#define cache_lock() pthread_mutex_lock(&cache_mutex) +#define cache_unlock() pthread_mutex_unlock(&cache_mutex) + +static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; +#define progress_lock() pthread_mutex_lock(&progress_mutex) +#define progress_unlock() pthread_mutex_unlock(&progress_mutex) + +#else + +#define read_lock() (void)0 +#define read_unlock() (void)0 +#define cache_lock() (void)0 +#define cache_unlock() (void)0 +#define progress_lock() (void)0 +#define progress_unlock() (void)0 + +#endif + /* * We search for deltas _backwards_ in a list sorted by type and * by size, so that we see progressively smaller and smaller files. @@ -1300,7 +1326,7 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, * one. */ static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth) + unsigned max_depth, unsigned long *mem_usage) { struct object_entry *trg_entry = trg->entry; struct object_entry *src_entry = src->entry; @@ -1313,12 +1339,6 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, if (trg_entry->type != src_entry->type) return -1; - /* We do not compute delta to *create* objects we are not - * going to pack. - */ - if (trg_entry->preferred_base) - return -1; - /* * We do not bother to try a delta that we discarded * on an earlier try, but only when reusing delta data. @@ -1355,24 +1375,28 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* Load data if not already done */ if (!trg->data) { + read_lock(); trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz); + read_unlock(); if (!trg->data) die("object %s cannot be read", sha1_to_hex(trg_entry->idx.sha1)); if (sz != trg_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(trg_entry->idx.sha1), sz, trg_size); - window_memory_usage += sz; + *mem_usage += sz; } if (!src->data) { + read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); + read_unlock(); if (!src->data) die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); - window_memory_usage += sz; + *mem_usage += sz; } if (!src->index) { src->index = create_delta_index(src->data, src_size); @@ -1382,7 +1406,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, warning("suboptimal pack - out of memory"); return 0; } - window_memory_usage += sizeof_delta_index(src->index); + *mem_usage += sizeof_delta_index(src->index); } delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); @@ -1402,17 +1426,27 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, trg_entry->delta_size = delta_size; trg->depth = src->depth + 1; + /* + * Handle memory allocation outside of the cache + * accounting lock. Compiler will optimize the strangeness + * away when THREADED_DELTA_SEARCH is not defined. + */ + if (trg_entry->delta_data) + free(trg_entry->delta_data); + cache_lock(); if (trg_entry->delta_data) { delta_cache_size -= trg_entry->delta_size; - free(trg_entry->delta_data); trg_entry->delta_data = NULL; } - if (delta_cacheable(src_size, trg_size, delta_size)) { - trg_entry->delta_data = xrealloc(delta_buf, delta_size); delta_cache_size += trg_entry->delta_size; - } else + cache_unlock(); + trg_entry->delta_data = xrealloc(delta_buf, delta_size); + } else { + cache_unlock(); free(delta_buf); + } + return 1; } @@ -1429,68 +1463,59 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) return m; } -static void free_unpacked(struct unpacked *n) +static unsigned long free_unpacked(struct unpacked *n) { - window_memory_usage -= sizeof_delta_index(n->index); + unsigned long freed_mem = sizeof_delta_index(n->index); free_delta_index(n->index); n->index = NULL; if (n->data) { + freed_mem += n->entry->size; free(n->data); n->data = NULL; - window_memory_usage -= n->entry->size; } n->entry = NULL; n->depth = 0; + return freed_mem; } -static void find_deltas(struct object_entry **list, int window, int depth) +static void find_deltas(struct object_entry **list, unsigned list_size, + int window, int depth, unsigned *processed) { - uint32_t i = nr_objects, idx = 0, count = 0, processed = 0; + uint32_t i = list_size, idx = 0, count = 0; unsigned int array_size = window * sizeof(struct unpacked); struct unpacked *array; - int max_depth; + unsigned long mem_usage = 0; - if (!nr_objects) - return; array = xmalloc(array_size); memset(array, 0, array_size); - if (progress) - start_progress(&progress_state, "Deltifying %u objects...", "", nr_result); do { struct object_entry *entry = list[--i]; struct unpacked *n = array + idx; - int j; - - if (!entry->preferred_base) - processed++; - - if (progress) - display_progress(&progress_state, processed); - - if (entry->delta) - /* This happens if we decided to reuse existing - * delta from a pack. "!no_reuse_delta &&" is implied. - */ - continue; + int j, max_depth, best_base = -1; - if (entry->size < 50) - continue; - - if (entry->no_try_delta) - continue; - - free_unpacked(n); + mem_usage -= free_unpacked(n); n->entry = entry; while (window_memory_limit && - window_memory_usage > window_memory_limit && + mem_usage > window_memory_limit && count > 1) { uint32_t tail = (idx + window - count) % window; - free_unpacked(array + tail); + mem_usage -= free_unpacked(array + tail); count--; } + /* We do not compute delta to *create* objects we are not + * going to pack. + */ + if (entry->preferred_base) + goto next; + + progress_lock(); + (*processed)++; + display_progress(progress_state, *processed); + progress_unlock(); + /* * If the current object is at pack edge, take the depth the * objects that depend on the current object into account @@ -1505,6 +1530,7 @@ static void find_deltas(struct object_entry **list, int window, int depth) j = window; while (--j > 0) { + int ret; uint32_t other_idx = idx + j; struct unpacked *m; if (other_idx >= window) @@ -1512,8 +1538,11 @@ static void find_deltas(struct object_entry **list, int window, int depth) m = array + other_idx; if (!m->entry) break; - if (try_delta(n, m, max_depth) < 0) + ret = try_delta(n, m, max_depth, &mem_usage); + if (ret < 0) break; + else if (ret > 0) + best_base = other_idx; } /* if we made n a delta, and if n is already at max @@ -1523,6 +1552,23 @@ static void find_deltas(struct object_entry **list, int window, int depth) if (entry->delta && depth <= n->depth) continue; + /* + * Move the best delta base up in the window, after the + * currently deltified object, to keep it longer. It will + * be the first base object to be attempted next. + */ + if (entry->delta) { + struct unpacked swap = array[best_base]; + int dist = (window + idx - best_base) % window; + int dst = best_base; + while (dist--) { + int src = (dst + 1) % window; + array[dst] = array[src]; + dst = src; + } + array[dst] = swap; + } + next: idx++; if (count + 1 < window) @@ -1531,9 +1577,6 @@ static void find_deltas(struct object_entry **list, int window, int depth) idx = 0; } while (i > 0); - if (progress) - stop_progress(&progress_state); - for (i = 0; i < window; ++i) { free_delta_index(array[i].index); free(array[i].data); @@ -1541,21 +1584,143 @@ static void find_deltas(struct object_entry **list, int window, int depth) free(array); } +#ifdef THREADED_DELTA_SEARCH + +struct thread_params { + pthread_t thread; + struct object_entry **list; + unsigned list_size; + int window; + int depth; + unsigned *processed; +}; + +static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER; +static struct thread_params *data_requester; + +static void *threaded_find_deltas(void *arg) +{ + struct thread_params *me = arg; + + for (;;) { + pthread_mutex_lock(&data_request); + data_requester = me; + pthread_mutex_unlock(&data_provider); + pthread_mutex_lock(&data_ready); + pthread_mutex_unlock(&data_request); + + if (!me->list_size) + return NULL; + + find_deltas(me->list, me->list_size, + me->window, me->depth, me->processed); + } +} + +static void ll_find_deltas(struct object_entry **list, unsigned list_size, + int window, int depth, unsigned *processed) +{ + struct thread_params *target, p[delta_search_threads]; + int i, ret; + unsigned chunk_size; + + if (delta_search_threads <= 1) { + find_deltas(list, list_size, window, depth, processed); + return; + } + + pthread_mutex_lock(&data_provider); + pthread_mutex_lock(&data_ready); + + for (i = 0; i < delta_search_threads; i++) { + p[i].window = window; + p[i].depth = depth; + p[i].processed = processed; + ret = pthread_create(&p[i].thread, NULL, + threaded_find_deltas, &p[i]); + if (ret) + die("unable to create thread: %s", strerror(ret)); + } + + /* this should be auto-tuned somehow */ + chunk_size = window * 1000; + + do { + unsigned sublist_size = chunk_size; + if (sublist_size > list_size) + sublist_size = list_size; + + /* try to split chunks on "path" boundaries */ + while (sublist_size < list_size && list[sublist_size]->hash && + list[sublist_size]->hash == list[sublist_size-1]->hash) + sublist_size++; + + pthread_mutex_lock(&data_provider); + target = data_requester; + target->list = list; + target->list_size = sublist_size; + pthread_mutex_unlock(&data_ready); + + list += sublist_size; + list_size -= sublist_size; + if (!sublist_size) { + pthread_join(target->thread, NULL); + i--; + } + } while (i); +} + +#else +#define ll_find_deltas find_deltas +#endif + static void prepare_pack(int window, int depth) { struct object_entry **delta_list; - uint32_t i; + uint32_t i, n, nr_deltas; get_object_details(); - if (!window || !depth) + if (!nr_objects || !window || !depth) return; delta_list = xmalloc(nr_objects * sizeof(*delta_list)); - for (i = 0; i < nr_objects; i++) - delta_list[i] = objects + i; - qsort(delta_list, nr_objects, sizeof(*delta_list), type_size_sort); - find_deltas(delta_list, window+1, depth); + nr_deltas = n = 0; + + for (i = 0; i < nr_objects; i++) { + struct object_entry *entry = objects + i; + + if (entry->delta) + /* This happens if we decided to reuse existing + * delta from a pack. "!no_reuse_delta &&" is implied. + */ + continue; + + if (entry->size < 50) + continue; + + if (entry->no_try_delta) + continue; + + if (!entry->preferred_base) + nr_deltas++; + + delta_list[n++] = entry; + } + + if (nr_deltas && n > 1) { + unsigned nr_done = 0; + if (progress) + progress_state = start_progress("Compressing objects", + nr_deltas); + qsort(delta_list, n, sizeof(*delta_list), type_size_sort); + ll_find_deltas(delta_list, n, window+1, depth, &nr_done); + stop_progress(&progress_state); + if (nr_done != nr_deltas) + die("inconsistency with delta count"); + } free(delta_list); } @@ -1591,6 +1756,23 @@ static int git_pack_config(const char *k, const char *v) cache_max_small_delta_size = git_config_int(k, v); return 0; } + if (!strcmp(k, "pack.threads")) { + delta_search_threads = git_config_int(k, v); + if (delta_search_threads < 1) + die("invalid number of threads specified (%d)", + delta_search_threads); +#ifndef THREADED_DELTA_SEARCH + if (delta_search_threads > 1) + warning("no threads support, ignoring %s", k); +#endif + return 0; + } + if (!strcmp(k, "pack.indexversion")) { + pack_idx_default_version = git_config_int(k, v); + if (pack_idx_default_version > 2) + die("bad pack.indexversion=%d", pack_idx_default_version); + return 0; + } return git_default_config(k, v); } @@ -1625,15 +1807,19 @@ static void read_object_list_from_stdin(void) } } +#define OBJECT_ADDED (1u<<20) + static void show_commit(struct commit *commit) { add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); + commit->object.flags |= OBJECT_ADDED; } static void show_object(struct object_array_entry *p) { add_preferred_base_object(p->name); add_object_entry(p->item->sha1, p->item->type, p->name, 0); + p->item->flags |= OBJECT_ADDED; } static void show_edge(struct commit *commit) @@ -1641,6 +1827,86 @@ static void show_edge(struct commit *commit) add_preferred_base(commit->object.sha1); } +struct in_pack_object { + off_t offset; + struct object *object; +}; + +struct in_pack { + int alloc; + int nr; + struct in_pack_object *array; +}; + +static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) +{ + in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p); + in_pack->array[in_pack->nr].object = object; + in_pack->nr++; +} + +/* + * Compare the objects in the offset order, in order to emulate the + * "git-rev-list --objects" output that produced the pack originally. + */ +static int ofscmp(const void *a_, const void *b_) +{ + struct in_pack_object *a = (struct in_pack_object *)a_; + struct in_pack_object *b = (struct in_pack_object *)b_; + + if (a->offset < b->offset) + return -1; + else if (a->offset > b->offset) + return 1; + else + return hashcmp(a->object->sha1, b->object->sha1); +} + +static void add_objects_in_unpacked_packs(struct rev_info *revs) +{ + struct packed_git *p; + struct in_pack in_pack; + uint32_t i; + + memset(&in_pack, 0, sizeof(in_pack)); + + for (p = packed_git; p; p = p->next) { + const unsigned char *sha1; + struct object *o; + + for (i = 0; i < revs->num_ignore_packed; i++) { + if (matches_pack_name(p, revs->ignore_packed[i])) + break; + } + if (revs->num_ignore_packed <= i) + continue; + if (open_pack_index(p)) + die("cannot open pack index"); + + ALLOC_GROW(in_pack.array, + in_pack.nr + p->num_objects, + in_pack.alloc); + + for (i = 0; i < p->num_objects; i++) { + sha1 = nth_packed_object_sha1(p, i); + o = lookup_unknown_object(sha1); + if (!(o->flags & OBJECT_ADDED)) + mark_in_pack_object(o, p, &in_pack); + o->flags |= OBJECT_ADDED; + } + } + + if (in_pack.nr) { + qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]), + ofscmp); + for (i = 0; i < in_pack.nr; i++) { + struct object *o = in_pack.array[i].object; + add_object_entry(o->sha1, o->type, "", 0); + } + } + free(in_pack.array); +} + static void get_object_list(int ac, const char **av) { struct rev_info revs; @@ -1672,6 +1938,9 @@ static void get_object_list(int ac, const char **av) prepare_revision_walk(&revs); mark_edges_uninteresting(revs.commits, &revs, show_edge); traverse_commit_list(&revs, show_commit, show_object); + + if (keep_unreachable) + add_objects_in_unpacked_packs(&revs); } static int adjust_perm(const char *path, mode_t mode) @@ -1750,6 +2019,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) usage(pack_usage); continue; } + if (!prefixcmp(arg, "--threads=")) { + char *end; + delta_search_threads = strtoul(arg+10, &end, 0); + if (!arg[10] || *end || delta_search_threads < 1) + usage(pack_usage); +#ifndef THREADED_DELTA_SEARCH + if (delta_search_threads > 1) + warning("no threads support, " + "ignoring %s", arg); +#endif + continue; + } if (!prefixcmp(arg, "--depth=")) { char *end; depth = strtoul(arg+8, &end, 0); @@ -1789,6 +2070,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) use_internal_rev_list = 1; continue; } + if (!strcmp("--keep-unreachable", arg)) { + keep_unreachable = 1; + continue; + } if (!strcmp("--unpacked", arg) || !prefixcmp(arg, "--unpacked=") || !strcmp("--reflog", arg) || @@ -1850,23 +2135,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) prepare_packed_git(); if (progress) - start_progress(&progress_state, "Generating pack...", - "Counting objects: ", 0); + progress_state = start_progress("Counting objects", 0); if (!use_internal_rev_list) read_object_list_from_stdin(); else { rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } - if (progress) { - stop_progress(&progress_state); - fprintf(stderr, "Done counting %u objects.\n", nr_objects); - } + stop_progress(&progress_state); if (non_empty && !nr_result) return 0; - if (progress && (nr_objects != nr_result)) - fprintf(stderr, "Result has %u objects.\n", nr_result); if (nr_result) prepare_pack(window, depth); write_pack_file(); diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 09df4e11a8..1923fb1914 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -3,9 +3,7 @@ #include "refs.h" #include "object.h" #include "tag.h" - -static const char builtin_pack_refs_usage[] = -"git-pack-refs [--all] [--prune | --no-prune]"; +#include "parse-options.h" struct ref_to_prune { struct ref_to_prune *next; @@ -117,31 +115,20 @@ static int pack_refs(unsigned int flags) return 0; } +static char const * const pack_refs_usage[] = { + "git-pack-refs [options]", + NULL +}; + int cmd_pack_refs(int argc, const char **argv, const char *prefix) { - int i; - unsigned int flags; - - flags = PACK_REFS_PRUNE; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--prune")) { - flags |= PACK_REFS_PRUNE; /* now the default */ - continue; - } - if (!strcmp(arg, "--no-prune")) { - flags &= ~PACK_REFS_PRUNE; - continue; - } - if (!strcmp(arg, "--all")) { - flags |= PACK_REFS_ALL; - continue; - } - /* perhaps other parameters later... */ - break; - } - if (i != argc) - usage(builtin_pack_refs_usage); - + unsigned int flags = PACK_REFS_PRUNE; + struct option opts[] = { + OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL), + OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE), + OPT_END(), + }; + if (parse_options(argc, argv, opts, pack_refs_usage, 0)) + usage_with_options(pack_refs_usage, opts); return pack_refs(flags); } diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index 977730064b..23faf3129f 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "progress.h" static const char prune_packed_usage[] = "git-prune-packed [-n] [-q]"; @@ -7,6 +8,8 @@ static const char prune_packed_usage[] = #define DRY_RUN 01 #define VERBOSE 02 +static struct progress *progress; + static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) { struct dirent *de; @@ -27,6 +30,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) printf("rm -f %s\n", pathname); else if (unlink(pathname) < 0) error("unable to unlink %s", pathname); + display_progress(progress, i + 1); } pathname[len] = 0; rmdir(pathname); @@ -39,6 +43,10 @@ void prune_packed_objects(int opts) const char *dir = get_object_directory(); int len = strlen(dir); + if (opts == VERBOSE) + progress = start_progress_delay("Removing duplicate objects", + 256, 95, 2); + if (len > PATH_MAX - 42) die("impossible object directory"); memcpy(pathname, dir, len); @@ -49,16 +57,12 @@ void prune_packed_objects(int opts) sprintf(pathname + len, "%02x/", i); d = opendir(pathname); - if (opts == VERBOSE && (d || i == 255)) - fprintf(stderr, "Removing unused objects %d%%...\015", - ((i+1) * 100) / 256); if (!d) continue; prune_dir(i, d, pathname, len + 3, opts); closedir(d); } - if (opts == VERBOSE) - fprintf(stderr, "\nDone.\n"); + stop_progress(&progress); } int cmd_prune_packed(int argc, const char **argv, const char *prefix) diff --git a/builtin-push.c b/builtin-push.c index 88c5024da7..41df717f84 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -6,10 +6,15 @@ #include "run-command.h" #include "builtin.h" #include "remote.h" +#include "transport.h" +#include "parse-options.h" -static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]"; +static const char * const push_usage[] = { + "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", + NULL, +}; -static int all, force, thin, verbose; +static int thin, verbose; static const char *receivepack; static const char **refspec; @@ -43,80 +48,40 @@ static void set_refspecs(const char **refs, int nr) } } -static int do_push(const char *repo) +static int do_push(const char *repo, int flags) { int i, errs; - int common_argc; - const char **argv; - int argc; struct remote *remote = remote_get(repo); if (!remote) die("bad repository '%s'", repo); - if (remote->receivepack) { - char *rp = xmalloc(strlen(remote->receivepack) + 16); - sprintf(rp, "--receive-pack=%s", remote->receivepack); - receivepack = rp; - } - if (!refspec && !all && remote->push_refspec_nr) { + if (!refspec + && !(flags & TRANSPORT_PUSH_ALL) + && remote->push_refspec_nr) { refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } - - argv = xmalloc((refspec_nr + 10) * sizeof(char *)); - argv[0] = "dummy-send-pack"; - argc = 1; - if (all) - argv[argc++] = "--all"; - if (force) - argv[argc++] = "--force"; - if (receivepack) - argv[argc++] = receivepack; - common_argc = argc; - errs = 0; - for (i = 0; i < remote->uri_nr; i++) { + for (i = 0; i < remote->url_nr; i++) { + struct transport *transport = + transport_get(remote, remote->url[i]); int err; - int dest_argc = common_argc; - int dest_refspec_nr = refspec_nr; - const char **dest_refspec = refspec; - const char *dest = remote->uri[i]; - const char *sender = "send-pack"; - if (!prefixcmp(dest, "http://") || - !prefixcmp(dest, "https://")) - sender = "http-push"; - else { - char *rem = xmalloc(strlen(remote->name) + 10); - sprintf(rem, "--remote=%s", remote->name); - argv[dest_argc++] = rem; - if (thin) - argv[dest_argc++] = "--thin"; - } - argv[0] = sender; - argv[dest_argc++] = dest; - while (dest_refspec_nr--) - argv[dest_argc++] = *dest_refspec++; - argv[dest_argc] = NULL; + if (receivepack) + transport_set_option(transport, + TRANS_OPT_RECEIVEPACK, receivepack); + if (thin) + transport_set_option(transport, TRANS_OPT_THIN, "yes"); + if (verbose) - fprintf(stderr, "Pushing to %s\n", dest); - err = run_command_v_opt(argv, RUN_GIT_CMD); + fprintf(stderr, "Pushing to %s\n", remote->url[i]); + err = transport_push(transport, refspec_nr, refspec, flags); + err |= transport_disconnect(transport); + if (!err) continue; - error("failed to push to '%s'", remote->uri[i]); - switch (err) { - case -ERR_RUN_COMMAND_FORK: - error("unable to fork for %s", sender); - case -ERR_RUN_COMMAND_EXEC: - error("unable to exec %s", sender); - break; - case -ERR_RUN_COMMAND_WAITPID: - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - error("%s died with strange error", sender); - } + error("failed to push to '%s'", remote->url[i]); errs++; } return !!errs; @@ -124,58 +89,55 @@ static int do_push(const char *repo) int cmd_push(int argc, const char **argv, const char *prefix) { - int i; + int flags = 0; + int all = 0; + int mirror = 0; + int dry_run = 0; + int force = 0; + int tags = 0; const char *repo = NULL; /* default repository */ - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + struct option options[] = { + OPT__VERBOSE(&verbose), + OPT_STRING( 0 , "repo", &repo, "repository", "repository"), + OPT_BOOLEAN( 0 , "all", &all, "push all refs"), + OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), + OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"), + OPT_BOOLEAN('f', "force", &force, "force updates"), + OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), + OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), + OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), + OPT_END() + }; - if (arg[0] != '-') { - repo = arg; - i++; - break; - } - if (!strcmp(arg, "-v")) { - verbose=1; - continue; - } - if (!prefixcmp(arg, "--repo=")) { - repo = arg+7; - continue; - } - if (!strcmp(arg, "--all")) { - all = 1; - continue; - } - if (!strcmp(arg, "--tags")) { - add_refspec("refs/tags/*"); - continue; - } - if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) { - force = 1; - continue; - } - if (!strcmp(arg, "--thin")) { - thin = 1; - continue; - } - if (!strcmp(arg, "--no-thin")) { - thin = 0; - continue; - } - if (!prefixcmp(arg, "--receive-pack=")) { - receivepack = arg; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - receivepack = arg; - continue; - } - usage(push_usage); + argc = parse_options(argc, argv, options, push_usage, 0); + + if (force) + flags |= TRANSPORT_PUSH_FORCE; + if (dry_run) + flags |= TRANSPORT_PUSH_DRY_RUN; + if (verbose) + flags |= TRANSPORT_PUSH_VERBOSE; + if (tags) + add_refspec("refs/tags/*"); + if (all) + flags |= TRANSPORT_PUSH_ALL; + if (mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if (argc > 0) { + repo = argv[0]; + set_refspecs(argv + 1, argc - 1); + } + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) + usage_with_options(push_usage, options); + + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == + (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { + error("--all and --mirror are incompatible"); + usage_with_options(push_usage, options); } - set_refspecs(argv + i, argc - i); - if (all && refspec) - usage(push_usage); - return do_push(repo); + return do_push(repo, flags); } diff --git a/builtin-rerere.c b/builtin-rerere.c index 29d057c98c..74493237c9 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -66,40 +66,15 @@ static int write_rr(struct path_list *rr, int out_fd) return commit_lock_file(&write_lock); } -struct buffer { - char *ptr; - int nr, alloc; -}; - -static void append_line(struct buffer *buffer, const char *line) -{ - int len = strlen(line); - - if (buffer->nr + len > buffer->alloc) { - buffer->alloc = alloc_nr(buffer->nr + len); - buffer->ptr = xrealloc(buffer->ptr, buffer->alloc); - } - memcpy(buffer->ptr + buffer->nr, line, len); - buffer->nr += len; -} - -static void clear_buffer(struct buffer *buffer) -{ - free(buffer->ptr); - buffer->ptr = NULL; - buffer->nr = buffer->alloc = 0; -} - static int handle_file(const char *path, unsigned char *sha1, const char *output) { SHA_CTX ctx; char buf[1024]; int hunk = 0, hunk_no = 0; - struct buffer minus = { NULL, 0, 0 }, plus = { NULL, 0, 0 }; - struct buffer *one = &minus, *two = + + struct strbuf one, two; FILE *f = fopen(path, "r"); - FILE *out; + FILE *out = NULL; if (!f) return error("Could not open %s", path); @@ -110,51 +85,50 @@ static int handle_file(const char *path, fclose(f); return error("Could not write %s", output); } - } else - out = NULL; + } if (sha1) SHA1_Init(&ctx); + strbuf_init(&one, 0); + strbuf_init(&two, 0); while (fgets(buf, sizeof(buf), f)) { if (!prefixcmp(buf, "<<<<<<< ")) hunk = 1; else if (!prefixcmp(buf, "=======")) hunk = 2; else if (!prefixcmp(buf, ">>>>>>> ")) { - int one_is_longer = (one->nr > two->nr); - int common_len = one_is_longer ? two->nr : one->nr; - int cmp = memcmp(one->ptr, two->ptr, common_len); + int cmp = strbuf_cmp(&one, &two); hunk_no++; hunk = 0; - if ((cmp > 0) || ((cmp == 0) && one_is_longer)) { - struct buffer *swap = one; - one = two; - two = swap; + if (cmp > 0) { + strbuf_swap(&one, &two); } if (out) { fputs("<<<<<<<\n", out); - fwrite(one->ptr, one->nr, 1, out); + fwrite(one.buf, one.len, 1, out); fputs("=======\n", out); - fwrite(two->ptr, two->nr, 1, out); + fwrite(two.buf, two.len, 1, out); fputs(">>>>>>>\n", out); } if (sha1) { - SHA1_Update(&ctx, one->ptr, one->nr); - SHA1_Update(&ctx, "\0", 1); - SHA1_Update(&ctx, two->ptr, two->nr); - SHA1_Update(&ctx, "\0", 1); + SHA1_Update(&ctx, one.buf ? one.buf : "", + one.len + 1); + SHA1_Update(&ctx, two.buf ? two.buf : "", + two.len + 1); } - clear_buffer(one); - clear_buffer(two); + strbuf_reset(&one); + strbuf_reset(&two); } else if (hunk == 1) - append_line(one, buf); + strbuf_addstr(&one, buf); else if (hunk == 2) - append_line(two, buf); + strbuf_addstr(&two, buf); else if (out) fputs(buf, out); } + strbuf_release(&one); + strbuf_release(&two); fclose(f); if (out) @@ -415,18 +389,39 @@ static int is_rerere_enabled(void) return 1; } -int cmd_rerere(int argc, const char **argv, const char *prefix) +static int setup_rerere(struct path_list *merge_rr) { - struct path_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd = -1; + int fd; git_config(git_rerere_config); if (!is_rerere_enabled()) - return 0; + return -1; merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); - read_rr(&merge_rr); + read_rr(merge_rr); + return fd; +} + +int rerere(void) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; + return do_plain_rerere(&merge_rr, fd); +} + +int cmd_rerere(int argc, const char **argv, const char *prefix) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int i, fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; if (argc < 2) return do_plain_rerere(&merge_rr, fd); diff --git a/builtin-reset.c b/builtin-reset.c new file mode 100644 index 0000000000..4c61025aae --- /dev/null +++ b/builtin-reset.c @@ -0,0 +1,290 @@ +/* + * "git reset" builtin command + * + * Copyright (c) 2007 Carlos Rica + * + * Based on git-reset.sh, which is + * + * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano + */ +#include "cache.h" +#include "tag.h" +#include "object.h" +#include "commit.h" +#include "run-command.h" +#include "refs.h" +#include "diff.h" +#include "diffcore.h" +#include "tree.h" + +static const char builtin_reset_usage[] = +"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]"; + +static char *args_to_str(const char **argv) +{ + char *buf = NULL; + unsigned long len, space = 0, nr = 0; + + for (; *argv; argv++) { + len = strlen(*argv); + ALLOC_GROW(buf, nr + 1 + len, space); + if (nr) + buf[nr++] = ' '; + memcpy(buf + nr, *argv, len); + nr += len; + } + ALLOC_GROW(buf, nr + 1, space); + buf[nr] = '\0'; + + return buf; +} + +static inline int is_merge(void) +{ + return !access(git_path("MERGE_HEAD"), F_OK); +} + +static int unmerged_files(void) +{ + int i; + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return 1; + } + return 0; +} + +static int reset_index_file(const unsigned char *sha1, int is_hard_reset) +{ + int i = 0; + const char *args[6]; + + args[i++] = "read-tree"; + args[i++] = "-v"; + args[i++] = "--reset"; + if (is_hard_reset) + args[i++] = "-u"; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static void print_new_head_line(struct commit *commit) +{ + const char *hex, *dots = "...", *body; + + hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + if (!hex) { + hex = sha1_to_hex(commit->object.sha1); + dots = ""; + } + printf("HEAD is now at %s%s", hex, dots); + body = strstr(commit->buffer, "\n\n"); + if (body) { + const char *eol; + size_t len; + body += 2; + eol = strchr(body, '\n'); + len = eol ? eol - body : strlen(body); + printf(" %.*s\n", (int) len, body); + } + else + printf("\n"); +} + +static int update_index_refresh(int fd, struct lock_file *index_lock) +{ + int result; + + if (!index_lock) { + index_lock = xcalloc(1, sizeof(struct lock_file)); + fd = hold_locked_index(index_lock, 1); + } + + if (read_cache() < 0) + return error("Could not read index"); + result = refresh_cache(0) ? 1 : 0; + if (write_cache(fd, active_cache, active_nr) || + close(fd) || + commit_locked_index(index_lock)) + return error ("Could not refresh index"); + return result; +} + +static void update_index_from_diff(struct diff_queue_struct *q, + struct diff_options *opt, void *data) +{ + int i; + int *discard_flag = data; + + /* do_diff_cache() mangled the index */ + discard_cache(); + *discard_flag = 1; + read_cache(); + + for (i = 0; i < q->nr; i++) { + struct diff_filespec *one = q->queue[i]->one; + if (one->mode) { + struct cache_entry *ce; + ce = make_cache_entry(one->mode, one->sha1, one->path, + 0, 0); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | + ADD_CACHE_OK_TO_REPLACE); + } else + remove_file_from_cache(one->path); + } +} + +static int read_from_tree(const char *prefix, const char **argv, + unsigned char *tree_sha1) +{ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int index_fd, index_was_discarded = 0; + struct diff_options opt; + + memset(&opt, 0, sizeof(opt)); + diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt); + opt.output_format = DIFF_FORMAT_CALLBACK; + opt.format_callback = update_index_from_diff; + opt.format_callback_data = &index_was_discarded; + + index_fd = hold_locked_index(lock, 1); + index_was_discarded = 0; + read_cache(); + if (do_diff_cache(tree_sha1, &opt)) + return 1; + diffcore_std(&opt); + diff_flush(&opt); + + if (!index_was_discarded) + /* The index is still clobbered from do_diff_cache() */ + discard_cache(); + return update_index_refresh(index_fd, lock); +} + +static void prepend_reflog_action(const char *action, char *buf, size_t size) +{ + const char *sep = ": "; + const char *rla = getenv("GIT_REFLOG_ACTION"); + if (!rla) + rla = sep = ""; + if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) + warning("Reflog action message too long: %.*s...", 50, buf); +} + +enum reset_type { MIXED, SOFT, HARD, NONE }; +static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL }; + +int cmd_reset(int argc, const char **argv, const char *prefix) +{ + int i = 1, reset_type = NONE, update_ref_status = 0, quiet = 0; + const char *rev = "HEAD"; + unsigned char sha1[20], *orig = NULL, sha1_orig[20], + *old_orig = NULL, sha1_old_orig[20]; + struct commit *commit; + char *reflog_action, msg[1024]; + + git_config(git_default_config); + + reflog_action = args_to_str(argv); + setenv("GIT_REFLOG_ACTION", reflog_action, 0); + + while (i < argc) { + if (!strcmp(argv[i], "--mixed")) { + reset_type = MIXED; + i++; + } + else if (!strcmp(argv[i], "--soft")) { + reset_type = SOFT; + i++; + } + else if (!strcmp(argv[i], "--hard")) { + reset_type = HARD; + i++; + } + else if (!strcmp(argv[i], "-q")) { + quiet = 1; + i++; + } + else + break; + } + + if (i < argc && argv[i][0] != '-') + rev = argv[i++]; + + if (get_sha1(rev, sha1)) + die("Failed to resolve '%s' as a valid ref.", rev); + + commit = lookup_commit_reference(sha1); + if (!commit) + die("Could not parse object '%s'.", rev); + hashcpy(sha1, commit->object.sha1); + + if (i < argc && !strcmp(argv[i], "--")) + i++; + else if (i < argc && argv[i][0] == '-') + usage(builtin_reset_usage); + + /* git reset tree [--] paths... can be used to + * load chosen paths from the tree into the index without + * affecting the working tree nor HEAD. */ + if (i < argc) { + if (reset_type == MIXED) + warning("--mixed option is deprecated with paths."); + else if (reset_type != NONE) + die("Cannot do %s reset with paths.", + reset_type_names[reset_type]); + return read_from_tree(prefix, argv + i, sha1); + } + if (reset_type == NONE) + reset_type = MIXED; /* by default */ + + /* Soft reset does not touch the index file nor the working tree + * at all, but requires them in a good order. Other resets reset + * the index file to the tree object we are switching to. */ + if (reset_type == SOFT) { + if (is_merge() || unmerged_files()) + die("Cannot do a soft reset in the middle of a merge."); + } + else if (reset_index_file(sha1, (reset_type == HARD))) + die("Could not reset index file to revision '%s'.", rev); + + /* Any resets update HEAD to the head being switched to, + * saving the previous head in ORIG_HEAD before. */ + if (!get_sha1("ORIG_HEAD", sha1_old_orig)) + old_orig = sha1_old_orig; + if (!get_sha1("HEAD", sha1_orig)) { + orig = sha1_orig; + prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg)); + update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + } + else if (old_orig) + delete_ref("ORIG_HEAD", old_orig); + prepend_reflog_action("updating HEAD", msg, sizeof(msg)); + update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); + + switch (reset_type) { + case HARD: + if (!update_ref_status && !quiet) + print_new_head_line(commit); + break; + case SOFT: /* Nothing else to do. */ + break; + case MIXED: /* Report what has not been updated. */ + update_index_refresh(0, NULL); + break; + } + + unlink(git_path("MERGE_HEAD")); + unlink(git_path("rr-cache/MERGE_RR")); + unlink(git_path("MERGE_MSG")); + unlink(git_path("SQUASH_MSG")); + + free(reflog_action); + + return update_ref_status; +} diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 9dbfae416c..1cb5f67119 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -9,6 +9,7 @@ #include "revision.h" #include "list-objects.h" #include "builtin.h" +#include "log-tree.h" /* bits #0-15 in revision.h */ @@ -25,6 +26,7 @@ static const char rev_list_usage[] = " --remove-empty\n" " --all\n" " --stdin\n" +" --quiet\n" " ordering output:\n" " --topo-order\n" " --date-order\n" @@ -38,7 +40,8 @@ static const char rev_list_usage[] = " --left-right\n" " special purpose:\n" " --bisect\n" -" --bisect-vars" +" --bisect-vars\n" +" --bisect-all" ; static struct rev_info revs; @@ -48,6 +51,7 @@ static int show_timestamp; static int hdr_termination; static const char *header_prefix; +static void finish_commit(struct commit *commit); static void show_commit(struct commit *commit) { if (show_timestamp) @@ -74,21 +78,28 @@ static void show_commit(struct commit *commit) parents = parents->next; } } + show_decorations(commit); if (revs.commit_format == CMIT_FMT_ONELINE) putchar(' '); else putchar('\n'); if (revs.verbose_header) { - char *buf = NULL; - unsigned long buflen = 0; - pretty_print_commit(revs.commit_format, commit, ~0, - &buf, &buflen, - revs.abbrev, NULL, NULL, revs.date_mode, 0); - printf("%s%c", buf, hdr_termination); - free(buf); + struct strbuf buf; + strbuf_init(&buf, 0); + pretty_print_commit(revs.commit_format, commit, + &buf, revs.abbrev, NULL, NULL, + revs.date_mode, 0); + if (buf.len) + printf("%s%c", buf.buf, hdr_termination); + strbuf_release(&buf); } maybe_flush_or_die(stdout, "stdout"); + finish_commit(commit); +} + +static void finish_commit(struct commit *commit) +{ if (commit->parents) { free_commit_list(commit->parents); commit->parents = NULL; @@ -97,6 +108,12 @@ static void show_commit(struct commit *commit) commit->buffer = NULL; } +static void finish_object(struct object_array_entry *p) +{ + if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1)) + die("missing blob object '%s'", sha1_to_hex(p->item->sha1)); +} + static void show_object(struct object_array_entry *p) { /* An object with name "foo\n0000000..." can be used to @@ -104,9 +121,7 @@ static void show_object(struct object_array_entry *p) */ const char *ep = strchr(p->name, '\n'); - if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1)) - die("missing blob object '%s'", sha1_to_hex(p->item->sha1)); - + finish_object(p); if (ep) { printf("%s %.*s\n", sha1_to_hex(p->item->sha1), (int) (ep - p->name), @@ -138,7 +153,7 @@ static int count_distance(struct commit_list *entry) if (commit->object.flags & (UNINTERESTING | COUNTED)) break; - if (!revs.prune_fn || (commit->object.flags & TREECHANGE)) + if (!(commit->object.flags & TREESAME)) nr++; commit->object.flags |= COUNTED; p = commit->parents; @@ -189,12 +204,12 @@ static int count_interesting_parents(struct commit *commit) return count; } -static inline int halfway(struct commit_list *p, int distance, int nr) +static inline int halfway(struct commit_list *p, int nr) { /* * Don't short-cut something we are not going to return! */ - if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) + if (p->item->object.flags & TREESAME) return 0; if (DEBUG_BISECT) return 0; @@ -202,8 +217,7 @@ static inline int halfway(struct commit_list *p, int distance, int nr) * 2 and 3 are halfway of 5. * 3 is halfway of 6 but 2 and 4 are not. */ - distance *= 2; - switch (distance - nr) { + switch (2 * weight(p) - nr) { case -1: case 0: case 1: return 1; default: @@ -231,7 +245,7 @@ static void show_list(const char *debug, int counted, int nr, char *ep, *sp; fprintf(stderr, "%c%c%c ", - (flags & TREECHANGE) ? 'T' : ' ', + (flags & TREESAME) ? ' ' : 'T', (flags & UNINTERESTING) ? 'U' : ' ', (flags & COUNTED) ? 'C' : ' '); if (commit->util) @@ -255,6 +269,81 @@ static void show_list(const char *debug, int counted, int nr, } #endif /* DEBUG_BISECT */ +static struct commit_list *best_bisection(struct commit_list *list, int nr) +{ + struct commit_list *p, *best; + int best_distance = -1; + + best = list; + for (p = list; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (flags & TREESAME) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + if (distance > best_distance) { + best = p; + best_distance = distance; + } + } + + return best; +} + +struct commit_dist { + struct commit *commit; + int distance; +}; + +static int compare_commit_dist(const void *a_, const void *b_) +{ + struct commit_dist *a, *b; + + a = (struct commit_dist *)a_; + b = (struct commit_dist *)b_; + if (a->distance != b->distance) + return b->distance - a->distance; /* desc sort */ + return hashcmp(a->commit->object.sha1, b->commit->object.sha1); +} + +static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +{ + struct commit_list *p; + struct commit_dist *array = xcalloc(nr, sizeof(*array)); + int cnt, i; + + for (p = list, cnt = 0; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (flags & TREESAME) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + array[cnt].commit = p->item; + array[cnt].distance = distance; + cnt++; + } + qsort(array, cnt, sizeof(*array), compare_commit_dist); + for (p = list, i = 0; i < cnt; i++) { + struct name_decoration *r = xmalloc(sizeof(*r) + 100); + struct object *obj = &(array[i].commit->object); + + sprintf(r->name, "dist=%d", array[i].distance); + r->next = add_decoration(&name_decoration, obj, r); + p->item = array[i].commit; + p = p->next; + } + if (p) + p->next = NULL; + free(array); + return list; +} + /* * zero or positive weight is the number of interesting commits it can * reach, including itself. Especially, weight = 0 means it does not @@ -268,39 +357,13 @@ static void show_list(const char *debug, int counted, int nr, * unknown. After running count_distance() first, they will get zero * or positive distance. */ - -static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all) +static struct commit_list *do_find_bisection(struct commit_list *list, + int nr, int *weights, + int find_all) { - int n, nr, on_list, counted, distance; - struct commit_list *p, *best, *next, *last; - int *weights; - - show_list("bisection 2 entry", 0, 0, list); - - /* - * Count the number of total and tree-changing items on the - * list, while reversing the list. - */ - for (nr = on_list = 0, last = NULL, p = list; - p; - p = next) { - unsigned flags = p->item->object.flags; - - next = p->next; - if (flags & UNINTERESTING) - continue; - p->next = last; - last = p; - if (!revs.prune_fn || (flags & TREECHANGE)) - nr++; - on_list++; - } - list = last; - show_list("bisection 2 sorted", 0, nr, list); + int n, counted; + struct commit_list *p; - *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); counted = 0; for (n = 0, p = list; p; p = p->next) { @@ -310,7 +373,7 @@ static struct commit_list *find_bisection(struct commit_list *list, p->item->util = &weights[n++]; switch (count_interesting_parents(commit)) { case 0: - if (!revs.prune_fn || (flags & TREECHANGE)) { + if (!(flags & TREESAME)) { weight_set(p, 1); counted++; show_list("bisection 2 count one", @@ -349,20 +412,14 @@ static struct commit_list *find_bisection(struct commit_list *list, for (p = list; p; p = p->next) { if (p->item->object.flags & UNINTERESTING) continue; - n = weight(p); - if (n != -2) + if (weight(p) != -2) continue; - distance = count_distance(p); + weight_set(p, count_distance(p)); clear_distance(list); - weight_set(p, distance); /* Does it happen to be at exactly half-way? */ - if (halfway(p, distance, nr)) { - p->next = NULL; - *reaches = distance; - free(weights); + if (!find_all && halfway(p, nr)) return p; - } counted++; } @@ -389,7 +446,7 @@ static struct commit_list *find_bisection(struct commit_list *list, * add one for p itself if p is to be counted, * otherwise inherit it from q directly. */ - if (!revs.prune_fn || (flags & TREECHANGE)) { + if (!(flags & TREESAME)) { weight_set(p, weight(q)+1); counted++; show_list("bisection 2 count one", @@ -399,37 +456,60 @@ static struct commit_list *find_bisection(struct commit_list *list, weight_set(p, weight(q)); /* Does it happen to be at exactly half-way? */ - distance = weight(p); - if (halfway(p, distance, nr)) { - p->next = NULL; - *reaches = distance; - free(weights); + if (!find_all && halfway(p, nr)) return p; - } } } show_list("bisection 2 counted all", counted, nr, list); - /* Then find the best one */ - counted = -1; - best = list; - for (p = list; p; p = p->next) { + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); +} + +static struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all, + int find_all) +{ + int nr, on_list; + struct commit_list *p, *best, *next, *last; + int *weights; + + show_list("bisection 2 entry", 0, 0, list); + + /* + * Count the number of total and tree-changing items on the + * list, while reversing the list. + */ + for (nr = on_list = 0, last = NULL, p = list; + p; + p = next) { unsigned flags = p->item->object.flags; - if (revs.prune_fn && !(flags & TREECHANGE)) + next = p->next; + if (flags & UNINTERESTING) continue; - distance = weight(p); - if (nr - distance < distance) - distance = nr - distance; - if (distance > counted) { - best = p; - counted = distance; - *reaches = weight(p); - } + p->next = last; + last = p; + if (!(flags & TREESAME)) + nr++; + on_list++; + } + list = last; + show_list("bisection 2 sorted", 0, nr, list); + + *all = nr; + weights = xcalloc(on_list, sizeof(*weights)); + + /* Do the real work of finding bisection commit. */ + best = do_find_bisection(list, nr, weights, find_all); + if (best) { + if (!find_all) + best->next = NULL; + *reaches = weight(best); } - if (best) - best->next = NULL; free(weights); return best; } @@ -457,6 +537,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int i; int read_from_stdin = 0; int bisect_show_vars = 0; + int bisect_find_all = 0; + int quiet = 0; git_config(git_default_config); init_revisions(&revs, prefix); @@ -479,6 +561,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--bisect-all")) { + bisect_list = 1; + bisect_find_all = 1; + continue; + } if (!strcmp(arg, "--bisect-vars")) { bisect_list = 1; bisect_show_vars = 1; @@ -490,6 +577,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) read_revisions_from_stdin(&revs); continue; } + if (!strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } usage(rev_list_usage); } @@ -525,9 +616,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) { int reaches = reaches, all = all; - revs.commits = find_bisection(revs.commits, &reaches, &all); + revs.commits = find_bisection(revs.commits, &reaches, &all, + bisect_find_all); if (bisect_show_vars) { int cnt; + char hex[41]; if (!revs.commits) return 1; /* @@ -539,15 +632,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) * A bisect set of size N has (N-1) commits further * to test, as we already know one bad one. */ - cnt = all-reaches; + cnt = all - reaches; if (cnt < reaches) cnt = reaches; + strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); + + if (bisect_find_all) { + traverse_commit_list(&revs, show_commit, show_object); + printf("------\n"); + } + printf("bisect_rev=%s\n" "bisect_nr=%d\n" "bisect_good=%d\n" "bisect_bad=%d\n" "bisect_all=%d\n", - sha1_to_hex(revs.commits->item->object.sha1), + hex, cnt - 1, all - reaches - 1, reaches - 1, @@ -556,7 +656,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } } - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(&revs, + quiet ? finish_commit : show_commit, + quiet ? finish_object : show_object); return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 8d78b69c90..d1038a0e66 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -8,6 +8,7 @@ #include "refs.h" #include "quote.h" #include "builtin.h" +#include "parse-options.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -209,13 +210,138 @@ static int try_difference(const char *arg) return 0; } +static int parseopt_dump(const struct option *o, const char *arg, int unset) +{ + struct strbuf *parsed = o->value; + if (unset) + strbuf_addf(parsed, " --no-%s", o->long_name); + else if (o->short_name) + strbuf_addf(parsed, " -%c", o->short_name); + else + strbuf_addf(parsed, " --%s", o->long_name); + if (arg) { + strbuf_addch(parsed, ' '); + sq_quote_buf(parsed, arg); + } + return 0; +} + +static const char *skipspaces(const char *s) +{ + while (isspace(*s)) + s++; + return s; +} + +static int cmd_parseopt(int argc, const char **argv, const char *prefix) +{ + static int keep_dashdash = 0; + static char const * const parseopt_usage[] = { + "git-rev-parse --parseopt [options] -- [<args>...]", + NULL + }; + static struct option parseopt_opts[] = { + OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash, + "keep the `--` passed as an arg"), + OPT_END(), + }; + + struct strbuf sb, parsed; + const char **usage = NULL; + struct option *opts = NULL; + int onb = 0, osz = 0, unb = 0, usz = 0; + + strbuf_init(&parsed, 0); + strbuf_addstr(&parsed, "set --"); + argc = parse_options(argc, argv, parseopt_opts, parseopt_usage, + PARSE_OPT_KEEP_DASHDASH); + if (argc < 1 || strcmp(argv[0], "--")) + usage_with_options(parseopt_usage, parseopt_opts); + + strbuf_init(&sb, 0); + /* get the usage up to the first line with a -- on it */ + for (;;) { + if (strbuf_getline(&sb, stdin, '\n') == EOF) + die("premature end of input"); + ALLOC_GROW(usage, unb + 1, usz); + if (!strcmp("--", sb.buf)) { + if (unb < 1) + die("no usage string given before the `--' separator"); + usage[unb] = NULL; + break; + } + usage[unb++] = strbuf_detach(&sb, NULL); + } + + /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */ + while (strbuf_getline(&sb, stdin, '\n') != EOF) { + const char *s; + struct option *o; + + if (!sb.len) + continue; + + ALLOC_GROW(opts, onb + 1, osz); + memset(opts + onb, 0, sizeof(opts[onb])); + + o = &opts[onb++]; + s = strchr(sb.buf, ' '); + if (!s || *sb.buf == ' ') { + o->type = OPTION_GROUP; + o->help = xstrdup(skipspaces(s)); + continue; + } + + o->type = OPTION_CALLBACK; + o->help = xstrdup(skipspaces(s)); + o->value = &parsed; + o->callback = &parseopt_dump; + switch (s[-1]) { + case '=': + s--; + break; + case '?': + o->flags = PARSE_OPT_OPTARG; + s--; + break; + default: + o->flags = PARSE_OPT_NOARG; + break; + } + + if (s - sb.buf == 1) /* short option only */ + o->short_name = *sb.buf; + else if (sb.buf[1] != ',') /* long option only */ + o->long_name = xmemdupz(sb.buf, s - sb.buf); + else { + o->short_name = *sb.buf; + o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); + } + } + strbuf_release(&sb); + + /* put an OPT_END() */ + ALLOC_GROW(opts, onb + 1, osz); + memset(opts + onb, 0, sizeof(opts[onb])); + argc = parse_options(argc, argv, opts, usage, + keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0); + + strbuf_addf(&parsed, " --"); + sq_quote_argv(&parsed, argv, argc, 0); + puts(parsed.buf); + return 0; +} + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0; unsigned char sha1[20]; - git_config(git_default_config); + if (argc > 1 && !strcmp("--parseopt", argv[1])) + return cmd_parseopt(argc - 1, argv + 1, prefix); + prefix = setup_git_directory(); + git_config(git_default_config); for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin-revert.c b/builtin-revert.c index 94e77771d2..a0586f9753 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -7,6 +7,7 @@ #include "run-command.h" #include "exec_cmd.h" #include "utf8.h" +#include "parse-options.h" /* * This implements the builtins revert and cherry-pick. @@ -19,51 +20,44 @@ * Copyright (c) 2005 Junio C Hamano */ -static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] <commit-ish>"; +static const char * const revert_usage[] = { + "git-revert [options] <commit-ish>", + NULL +}; -static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-r] [-x] <commit-ish>"; +static const char * const cherry_pick_usage[] = { + "git-cherry-pick [options] <commit-ish>", + NULL +}; -static int edit; -static int replay; +static int edit, no_replay, no_commit, mainline; static enum { REVERT, CHERRY_PICK } action; -static int no_commit; static struct commit *commit; -static int needed_deref; static const char *me; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -static void parse_options(int argc, const char **argv) +static void parse_args(int argc, const char **argv) { - const char *usage_str = action == REVERT ? - revert_usage : cherry_pick_usage; + const char * const * usage_str = + action == REVERT ? revert_usage : cherry_pick_usage; unsigned char sha1[20]; const char *arg; - int i; + int noop; + struct option options[] = { + OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), + OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), + OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"), + OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), + OPT_INTEGER('m', "mainline", &mainline, "parent number"), + OPT_END(), + }; + + if (parse_options(argc, argv, options, usage_str, 0) != 1) + usage_with_options(usage_str, options); + arg = argv[0]; - if (argc < 2) - usage(usage_str); - - for (i = 1; i < argc; i++) { - arg = argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit")) - no_commit = 1; - else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit")) - edit = 1; - else if (!strcmp(arg, "--no-edit")) - edit = 0; - else if (!strcmp(arg, "-x") || !strcmp(arg, "--i-really-want-" - "to-expose-my-private-commit-object-name")) - replay = 0; - else if (strcmp(arg, "-r")) - usage(usage_str); - } - if (i != argc - 1) - usage(usage_str); - arg = argv[argc - 1]; if (get_sha1(arg, sha1)) die ("Cannot find '%s'", arg); commit = (struct commit *)parse_object(sha1); @@ -72,7 +66,6 @@ static void parse_options(int argc, const char **argv) if (commit->object.type == OBJ_TAG) { commit = (struct commit *) deref_tag((struct object *)commit, arg, strlen(arg)); - needed_deref = 1; } if (commit->object.type != OBJ_COMMIT) die ("'%s' does not point to a commit", arg); @@ -168,9 +161,7 @@ static void set_author_ident_env(const char *message) char *line, *pend, *email, *timestamp; p += 7; - line = xmalloc(eol + 1 - p); - memcpy(line, p, eol - p); - line[eol - p] = '\0'; + line = xmemdupz(p, eol - p); email = strchr(line, '<'); if (!email) die ("Could not extract author email from %s", @@ -236,7 +227,7 @@ static int merge_recursive(const char *base_sha1, static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; - struct commit *base, *next; + struct commit *base, *next, *parent; int i; char *oneline, *reencoded_message = NULL; const char *message, *encoding; @@ -245,16 +236,18 @@ static int revert_or_cherry_pick(int argc, const char **argv) git_config(git_default_config); me = action == REVERT ? "revert" : "cherry-pick"; setenv(GIT_REFLOG_ACTION, me, 0); - parse_options(argc, argv); + parse_args(argc, argv); /* this is copied from the shell script, but it's never triggered... */ - if (action == REVERT && replay) + if (action == REVERT && !no_replay) die("revert is incompatible with replay"); if (no_commit) { /* * We do not intend to commit immediately. We just want to - * merge the differences in. + * merge the differences in, so let's compute the tree + * that represents the "current" state for merge-recursive + * to work on. */ if (write_tree(head, 0, NULL)) die ("Your index file is unmerged."); @@ -271,8 +264,29 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (!commit->parents) die ("Cannot %s a root commit", me); - if (commit->parents->next) - die ("Cannot %s a multi-parent commit.", me); + if (commit->parents->next) { + /* Reverting or cherry-picking a merge commit */ + int cnt; + struct commit_list *p; + + if (!mainline) + die("Commit %s is a merge but no -m option was given.", + sha1_to_hex(commit->object.sha1)); + + for (cnt = 1, p = commit->parents; + cnt != mainline && p; + cnt++) + p = p->next; + if (cnt != mainline || !p) + die("Commit %s does not have parent %d", + sha1_to_hex(commit->object.sha1), mainline); + parent = p->item; + } else if (0 < mainline) + die("Mainline was specified but commit %s is not a merge.", + sha1_to_hex(commit->object.sha1)); + else + parent = commit->parents->item; + if (!(message = commit->buffer)) die ("Cannot get commit message for %s", sha1_to_hex(commit->object.sha1)); @@ -301,34 +315,23 @@ static int revert_or_cherry_pick(int argc, const char **argv) char *oneline_body = strchr(oneline, ' '); base = commit; - next = commit->parents->item; + next = parent; add_to_msg("Revert \""); add_to_msg(oneline_body + 1); add_to_msg("\"\n\nThis reverts commit "); add_to_msg(sha1_to_hex(commit->object.sha1)); add_to_msg(".\n"); } else { - base = commit->parents->item; + base = parent; next = commit; set_author_ident_env(message); add_message_to_msg(message); - if (!replay) { + if (no_replay) { add_to_msg("(cherry picked from commit "); add_to_msg(sha1_to_hex(commit->object.sha1)); add_to_msg(")\n"); } } - if (needed_deref) { - add_to_msg("(original 'git "); - add_to_msg(me); - add_to_msg("' arguments: "); - for (i = 0; i < argc; i++) { - if (i) - add_to_msg(" "); - add_to_msg(argv[i]); - } - add_to_msg(")\n"); - } if (merge_recursive(sha1_to_hex(base->object.sha1), sha1_to_hex(head), "HEAD", @@ -390,13 +393,14 @@ int cmd_revert(int argc, const char **argv, const char *prefix) { if (isatty(0)) edit = 1; + no_replay = 1; action = REVERT; return revert_or_cherry_pick(argc, argv); } int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - replay = 1; + no_replay = 0; action = CHERRY_PICK; return revert_or_cherry_pick(argc, argv); } diff --git a/builtin-rm.c b/builtin-rm.c index 9a808c1bf9..a3d25e6a57 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -8,9 +8,12 @@ #include "dir.h" #include "cache-tree.h" #include "tree-walk.h" +#include "parse-options.h" -static const char builtin_rm_usage[] = -"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>..."; +static const char * const builtin_rm_usage[] = { + "git-rm [options] [--] <file>...", + NULL +}; static struct { int nr, alloc; @@ -121,11 +124,23 @@ static int check_local_mod(unsigned char *head, int index_only) static struct lock_file lock_file; +static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; +static int ignore_unmatch = 0; + +static struct option builtin_rm_options[] = { + OPT__DRY_RUN(&show_only), + OPT__QUIET(&quiet), + OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"), + OPT_BOOLEAN('f', NULL, &force, "override the up-to-date check"), + OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"), + OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, + "exit with a zero status even if nothing matched"), + OPT_END(), +}; + int cmd_rm(int argc, const char **argv, const char *prefix) { int i, newfd; - int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; - int ignore_unmatch = 0; const char **pathspec; char *seen; @@ -136,34 +151,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die("index file corrupt"); - for (i = 1 ; i < argc ; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0); + if (!argc) + usage_with_options(builtin_rm_usage, builtin_rm_options); - if (*arg != '-') - break; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (!strcmp(arg, "-n")) - show_only = 1; - else if (!strcmp(arg, "--cached")) - index_only = 1; - else if (!strcmp(arg, "-f")) - force = 1; - else if (!strcmp(arg, "-r")) - recursive = 1; - else if (!strcmp(arg, "--quiet")) - quiet = 1; - else if (!strcmp(arg, "--ignore-unmatch")) - ignore_unmatch = 1; - else - usage(builtin_rm_usage); - } - if (argc <= i) - usage(builtin_rm_usage); + if (!index_only) + setup_work_tree(); - pathspec = get_pathspec(prefix, argv + i); + pathspec = get_pathspec(prefix, argv); seen = NULL; for (i = 0; pathspec[i] ; i++) /* nothing */; @@ -227,7 +222,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (remove_file_from_cache(path)) die("git-rm: unable to remove %s", path); - cache_tree_invalidate_path(active_cache_tree, path); } if (show_only) diff --git a/builtin-send-pack.c b/builtin-send-pack.c new file mode 100644 index 0000000000..25ae1fe860 --- /dev/null +++ b/builtin-send-pack.c @@ -0,0 +1,652 @@ +#include "cache.h" +#include "commit.h" +#include "tag.h" +#include "refs.h" +#include "pkt-line.h" +#include "run-command.h" +#include "remote.h" +#include "send-pack.h" + +static const char send_pack_usage[] = +"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" +" --all and explicit <ref> specification are mutually exclusive."; + +static struct send_pack_args args = { + /* .receivepack = */ "git-receive-pack", +}; + +/* + * Make a pack stream and spit it out into file descriptor fd + */ +static int pack_objects(int fd, struct ref *refs) +{ + /* + * The child becomes pack-objects --revs; we feed + * the revision parameters to it via its stdin and + * let its stdout go back to the other end. + */ + const char *argv[] = { + "pack-objects", + "--all-progress", + "--revs", + "--stdout", + NULL, + NULL, + }; + struct child_process po; + + if (args.use_thin_pack) + argv[4] = "--thin"; + memset(&po, 0, sizeof(po)); + po.argv = argv; + po.in = -1; + po.out = fd; + po.git_cmd = 1; + if (start_command(&po)) + die("git-pack-objects failed (%s)", strerror(errno)); + + /* + * We feed the pack-objects we just spawned with revision + * parameters by writing to the pipe. + */ + while (refs) { + char buf[42]; + + if (!is_null_sha1(refs->old_sha1) && + has_sha1_file(refs->old_sha1)) { + memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); + buf[0] = '^'; + buf[41] = '\n'; + if (!write_or_whine(po.in, buf, 42, + "send-pack: send refs")) + break; + } + if (!is_null_sha1(refs->new_sha1)) { + memcpy(buf, sha1_to_hex(refs->new_sha1), 40); + buf[40] = '\n'; + if (!write_or_whine(po.in, buf, 41, + "send-pack: send refs")) + break; + } + refs = refs->next; + } + + if (finish_command(&po)) + return error("pack-objects died with strange error"); + return 0; +} + +static void unmark_and_free(struct commit_list *list, unsigned int mark) +{ + while (list) { + struct commit_list *temp = list; + temp->item->object.flags &= ~mark; + list = temp->next; + free(temp); + } +} + +static int ref_newer(const unsigned char *new_sha1, + const unsigned char *old_sha1) +{ + struct object *o; + struct commit *old, *new; + struct commit_list *list, *used; + int found = 0; + + /* Both new and old must be commit-ish and new is descendant of + * old. Otherwise we require --force. + */ + o = deref_tag(parse_object(old_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + old = (struct commit *) o; + + o = deref_tag(parse_object(new_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + new = (struct commit *) o; + + if (parse_commit(new) < 0) + return 0; + + used = list = NULL; + commit_list_insert(new, &list); + while (list) { + new = pop_most_recent_commit(&list, 1); + commit_list_insert(new, &used); + if (new == old) { + found = 1; + break; + } + } + unmark_and_free(list, 1); + unmark_and_free(used, 1); + return found; +} + +static struct ref *local_refs, **local_tail; +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; + ref = xcalloc(1, sizeof(*ref) + len); + hashcpy(ref->new_sha1, sha1); + memcpy(ref->name, refname, len); + *local_tail = ref; + local_tail = &ref->next; + return 0; +} + +static void get_local_heads(void) +{ + local_tail = &local_refs; + for_each_ref(one_local_ref, NULL); +} + +static int receive_status(int in, struct ref *refs) +{ + struct ref *hint; + char line[1000]; + int ret = 0; + int len = packet_read_line(in, line, sizeof(line)); + if (len < 10 || memcmp(line, "unpack ", 7)) + return error("did not receive remote status"); + if (memcmp(line, "unpack ok\n", 10)) { + char *p = line + strlen(line) - 1; + if (*p == '\n') + *p = '\0'; + error("unpack failed: %s", line + 7); + ret = -1; + } + hint = NULL; + while (1) { + char *refname; + char *msg; + len = packet_read_line(in, line, sizeof(line)); + if (!len) + break; + if (len < 3 || + (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { + fprintf(stderr, "protocol error: %s\n", line); + ret = -1; + break; + } + + line[strlen(line)-1] = '\0'; + refname = line + 3; + msg = strchr(refname, ' '); + if (msg) + *msg++ = '\0'; + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_ref_by_name(hint, refname); + if (!hint) + hint = find_ref_by_name(refs, refname); + if (!hint) { + warning("remote reported status on unknown ref: %s", + refname); + continue; + } + if (hint->status != REF_STATUS_EXPECTING_REPORT) { + warning("remote reported status on unexpected ref: %s", + refname); + continue; + } + + if (line[0] == 'o' && line[1] == 'k') + hint->status = REF_STATUS_OK; + else { + hint->status = REF_STATUS_REMOTE_REJECT; + ret = -1; + } + if (msg) + hint->remote_status = xstrdup(msg); + /* start our next search from the next ref */ + hint = hint->next; + } + return ret; +} + +static void update_tracking_ref(struct remote *remote, struct ref *ref) +{ + struct refspec rs; + + if (ref->status != REF_STATUS_OK) + return; + + rs.src = ref->name; + rs.dst = NULL; + + if (!remote_find_tracking(remote, &rs)) { + if (args.verbose) + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (ref->deletion) { + if (delete_ref(rs.dst, NULL)) + error("Failed to delete"); + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); + free(rs.dst); + } +} + +static const char *prettify_ref(const struct ref *ref) +{ + const char *name = ref->name; + return name + ( + !prefixcmp(name, "refs/heads/") ? 11 : + !prefixcmp(name, "refs/tags/") ? 10 : + !prefixcmp(name, "refs/remotes/") ? 13 : + 0); +} + +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) + +static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) +{ + fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + if (from) + fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to)); + else + fputs(prettify_ref(to), stderr); + if (msg) { + fputs(" (", stderr); + fputs(msg, stderr); + fputc(')', stderr); + } + fputc('\n', stderr); +} + +static const char *status_abbrev(unsigned char sha1[20]) +{ + const char *abbrev; + abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV); + return abbrev ? abbrev : sha1_to_hex(sha1); +} + +static void print_ok_ref_status(struct ref *ref) +{ + if (ref->deletion) + print_ref_status('-', "[deleted]", ref, NULL, NULL); + else if (is_null_sha1(ref->old_sha1)) + print_ref_status('*', + (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : + "[new branch]"), + ref, ref->peer_ref, NULL); + else { + char quickref[84]; + char type; + const char *msg; + + strcpy(quickref, status_abbrev(ref->old_sha1)); + if (ref->nonfastforward) { + strcat(quickref, "..."); + type = '+'; + msg = "forced update"; + } else { + strcat(quickref, ".."); + type = ' '; + msg = NULL; + } + strcat(quickref, status_abbrev(ref->new_sha1)); + + print_ref_status(type, quickref, ref, ref->peer_ref, msg); + } +} + +static int print_one_push_status(struct ref *ref, const char *dest, int count) +{ + if (!count) + fprintf(stderr, "To %s\n", dest); + + switch(ref->status) { + case REF_STATUS_NONE: + print_ref_status('X', "[no match]", ref, NULL, NULL); + break; + case REF_STATUS_REJECT_NODELETE: + print_ref_status('!', "[rejected]", ref, NULL, + "remote does not support deleting refs"); + break; + case REF_STATUS_UPTODATE: + print_ref_status('=', "[up to date]", ref, + ref->peer_ref, NULL); + break; + case REF_STATUS_REJECT_NONFASTFORWARD: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "non-fast forward"); + break; + case REF_STATUS_REMOTE_REJECT: + print_ref_status('!', "[remote rejected]", ref, + ref->deletion ? NULL : ref->peer_ref, + ref->remote_status); + break; + case REF_STATUS_EXPECTING_REPORT: + print_ref_status('!', "[remote failure]", ref, + ref->deletion ? NULL : ref->peer_ref, + "remote failed to report status"); + break; + case REF_STATUS_OK: + print_ok_ref_status(ref); + break; + } + + return 1; +} + +static void print_push_status(const char *dest, struct ref *refs) +{ + struct ref *ref; + int n = 0; + + if (args.verbose) { + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_UPTODATE) + n += print_one_push_status(ref, dest, n); + } + + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + + for (ref = refs; ref; ref = ref->next) { + if (ref->status != REF_STATUS_NONE && + ref->status != REF_STATUS_UPTODATE && + ref->status != REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + } +} + +static int refs_pushed(struct ref *ref) +{ + for (; ref; ref = ref->next) { + switch(ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + break; + default: + return 1; + } + } + return 0; +} + +static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec) +{ + struct ref *ref; + int new_refs; + int ask_for_status_report = 0; + int allow_deleting_refs = 0; + int expect_status_report = 0; + int flags = MATCH_REFS_NONE; + int ret; + + if (args.send_all) + flags |= MATCH_REFS_ALL; + if (args.send_mirror) + flags |= MATCH_REFS_MIRROR; + + /* No funny business with the matcher */ + remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); + get_local_heads(); + + /* Does the other end support the reporting? */ + if (server_supports("report-status")) + ask_for_status_report = 1; + if (server_supports("delete-refs")) + allow_deleting_refs = 1; + + /* match them up */ + if (!remote_tail) + remote_tail = &remote_refs; + if (match_refs(local_refs, remote_refs, &remote_tail, + nr_refspec, refspec, flags)) + return -1; + + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); + return 0; + } + + /* + * Finally, tell the other end! + */ + new_refs = 0; + for (ref = remote_refs; ref; ref = ref->next) { + const unsigned char *new_sha1; + + if (!ref->peer_ref) { + if (!args.send_mirror) + continue; + new_sha1 = null_sha1; + } + else + new_sha1 = ref->peer_ref->new_sha1; + + + ref->deletion = is_null_sha1(new_sha1); + if (ref->deletion && !allow_deleting_refs) { + ref->status = REF_STATUS_REJECT_NODELETE; + continue; + } + if (!ref->deletion && + !hashcmp(ref->old_sha1, new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + /* This part determines what can overwrite what. + * The rules are: + * + * (0) you can always use --force or +A:B notation to + * selectively force individual ref pairs. + * + * (1) if the old thing does not exist, it is OK. + * + * (2) if you do not have the old thing, you are not allowed + * to overwrite it; you would not know what you are losing + * otherwise. + * + * (3) if both new and old are commit-ish, and new is a + * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. + */ + + ref->nonfastforward = + !ref->deletion && + !is_null_sha1(ref->old_sha1) && + (!has_sha1_file(ref->old_sha1) + || !ref_newer(new_sha1, ref->old_sha1)); + + if (ref->nonfastforward && !ref->force && !args.force_update) { + ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + continue; + } + + hashcpy(ref->new_sha1, new_sha1); + if (!ref->deletion) + new_refs++; + + if (!args.dry_run) { + char *old_hex = sha1_to_hex(ref->old_sha1); + char *new_hex = sha1_to_hex(ref->new_sha1); + + if (ask_for_status_report) { + packet_write(out, "%s %s %s%c%s", + old_hex, new_hex, ref->name, 0, + "report-status"); + ask_for_status_report = 0; + expect_status_report = 1; + } + else + packet_write(out, "%s %s %s", + old_hex, new_hex, ref->name); + } + ref->status = expect_status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + } + + packet_flush(out); + if (new_refs && !args.dry_run) { + if (pack_objects(out, remote_refs) < 0) { + close(out); + return -1; + } + } + close(out); + + if (expect_status_report) + ret = receive_status(in, remote_refs); + else + ret = 0; + + print_push_status(dest, remote_refs); + + if (!args.dry_run && remote) { + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(remote, ref); + } + + if (!refs_pushed(remote_refs)) + fprintf(stderr, "Everything up-to-date\n"); + if (ret < 0) + return ret; + for (ref = remote_refs; ref; ref = ref->next) { + switch (ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + case REF_STATUS_OK: + break; + default: + return -1; + } + } + return 0; +} + +static void verify_remote_names(int nr_heads, const char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + case -3: /* ok but ends with a pattern-match character */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} + +int cmd_send_pack(int argc, const char **argv, const char *prefix) +{ + int i, nr_heads = 0; + const char **heads = NULL; + const char *remote_name = NULL; + struct remote *remote = NULL; + const char *dest = NULL; + + argv++; + for (i = 1; i < argc; i++, argv++) { + const char *arg = *argv; + + if (*arg == '-') { + if (!prefixcmp(arg, "--receive-pack=")) { + args.receivepack = arg + 15; + continue; + } + if (!prefixcmp(arg, "--exec=")) { + args.receivepack = arg + 7; + continue; + } + if (!prefixcmp(arg, "--remote=")) { + remote_name = arg + 9; + continue; + } + if (!strcmp(arg, "--all")) { + args.send_all = 1; + continue; + } + if (!strcmp(arg, "--dry-run")) { + args.dry_run = 1; + continue; + } + if (!strcmp(arg, "--mirror")) { + args.send_mirror = 1; + continue; + } + if (!strcmp(arg, "--force")) { + args.force_update = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + args.verbose = 1; + continue; + } + if (!strcmp(arg, "--thin")) { + args.use_thin_pack = 1; + continue; + } + usage(send_pack_usage); + } + if (!dest) { + dest = arg; + continue; + } + heads = (const char **) argv; + nr_heads = argc - i; + break; + } + if (!dest) + usage(send_pack_usage); + /* + * --all and --mirror are incompatible; neither makes sense + * with any refspecs. + */ + if ((heads && (args.send_all || args.send_mirror)) || + (args.send_all && args.send_mirror)) + usage(send_pack_usage); + + if (remote_name) { + remote = remote_get(remote_name); + if (!remote_has_url(remote, dest)) { + die("Destination %s is not a uri for %s", + dest, remote_name); + } + } + + return send_pack(&args, dest, remote, nr_heads, heads); +} + +int send_pack(struct send_pack_args *my_args, + const char *dest, struct remote *remote, + int nr_heads, const char **heads) +{ + int fd[2], ret; + struct child_process *conn; + + memcpy(&args, my_args, sizeof(args)); + + verify_remote_names(nr_heads, heads); + + conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0); + ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads); + close(fd[0]); + close(fd[1]); + ret |= finish_connect(conn); + return !!ret; +} diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 16af6199ab..3fe754677d 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -39,10 +39,7 @@ static void insert_author_oneline(struct path_list *list, while (authorlen > 0 && isspace(author[authorlen - 1])) authorlen--; - buffer = xmalloc(authorlen + 1); - memcpy(buffer, author, authorlen); - buffer[authorlen] = '\0'; - + buffer = xmemdupz(author, authorlen); item = path_list_insert(buffer, list); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct path_list)); @@ -66,13 +63,9 @@ static void insert_author_oneline(struct path_list *list, oneline++; onelinelen--; } - while (onelinelen > 0 && isspace(oneline[onelinelen - 1])) onelinelen--; - - buffer = xmalloc(onelinelen + 1); - memcpy(buffer, oneline, onelinelen); - buffer[onelinelen] = '\0'; + buffer = xmemdupz(oneline, onelinelen); if (dot3) { int dot3len = strlen(dot3); diff --git a/builtin-show-branch.c b/builtin-show-branch.c index b9cf1b379f..6dc835d30a 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -259,16 +259,15 @@ static void join_revs(struct commit_list **list_p, static void show_one_commit(struct commit *commit, int no_name) { - char *pretty = NULL; + struct strbuf pretty; const char *pretty_str = "(unavailable)"; - unsigned long pretty_len = 0; struct commit_name *name = commit->util; + strbuf_init(&pretty, 0); if (commit->object.parsed) { - pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - &pretty, &pretty_len, - 0, NULL, NULL, 0, 0); - pretty_str = pretty; + pretty_print_commit(CMIT_FMT_ONELINE, commit, + &pretty, 0, NULL, NULL, 0, 0); + pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) pretty_str += 8; @@ -289,7 +288,7 @@ static void show_one_commit(struct commit *commit, int no_name) find_unique_abbrev(commit->object.sha1, 7)); } puts(pretty_str); - free(pretty); + strbuf_release(&pretty); } static char *ref_name[MAX_REVS + 1]; diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 916355ca5d..c0b21301ba 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -8,17 +8,13 @@ */ static size_t cleanup(char *line, size_t len) { - if (len) { - if (line[len - 1] == '\n') - len--; - - while (len) { - unsigned char c = line[len - 1]; - if (!isspace(c)) - break; - len--; - } + while (len) { + unsigned char c = line[len - 1]; + if (!isspace(c)) + break; + len--; } + return len; } @@ -34,66 +30,60 @@ static size_t cleanup(char *line, size_t len) * If the input has only empty lines and spaces, * no output will be produced. * - * If last line has a newline at the end, it will be removed. + * If last line does not have a newline at the end, one is added. * * Enable skip_comments to skip every line starting with "#". */ -size_t stripspace(char *buffer, size_t length, int skip_comments) +void stripspace(struct strbuf *sb, int skip_comments) { - int empties = -1; + int empties = 0; size_t i, j, len, newlen; char *eol; - for (i = j = 0; i < length; i += len, j += newlen) { - eol = memchr(buffer + i, '\n', length - i); - len = eol ? eol - (buffer + i) + 1 : length - i; + /* We may have to add a newline. */ + strbuf_grow(sb, 1); + + for (i = j = 0; i < sb->len; i += len, j += newlen) { + eol = memchr(sb->buf + i, '\n', sb->len - i); + len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; - if (skip_comments && len && buffer[i] == '#') { + if (skip_comments && len && sb->buf[i] == '#') { newlen = 0; continue; } - newlen = cleanup(buffer + i, len); + newlen = cleanup(sb->buf + i, len); /* Not just an empty line? */ if (newlen) { - if (empties != -1) - buffer[j++] = '\n'; - if (empties > 0) - buffer[j++] = '\n'; + if (empties > 0 && j > 0) + sb->buf[j++] = '\n'; empties = 0; - memmove(buffer + j, buffer + i, newlen); - continue; + memmove(sb->buf + j, sb->buf + i, newlen); + sb->buf[newlen + j++] = '\n'; + } else { + empties++; } - if (empties < 0) - continue; - empties++; } - return j; + strbuf_setlen(sb, j); } int cmd_stripspace(int argc, const char **argv, const char *prefix) { - char *buffer; - unsigned long size; + struct strbuf buf; int strip_comments = 0; if (argc > 1 && (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; - size = 1024; - buffer = xmalloc(size); - if (read_fd(0, &buffer, &size)) { - free(buffer); + strbuf_init(&buf, 0); + if (strbuf_read(&buf, 0, 1024) < 0) die("could not read the input"); - } - size = stripspace(buffer, size, strip_comments); - write_or_die(1, buffer, size); - if (size) - putc('\n', stdout); + stripspace(&buf, strip_comments); - free(buffer); + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); return 0; } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 9eb95e50da..d33982b967 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "refs.h" +#include "parse-options.h" -static const char git_symbolic_ref_usage[] = -"git-symbolic-ref [-q] [-m <reason>] name [ref]"; +static const char * const git_symbolic_ref_usage[] = { + "git-symbolic-ref [options] name [ref]", + NULL +}; static void check_symref(const char *HEAD, int quiet) { @@ -26,44 +29,25 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { int quiet = 0; const char *msg = NULL; + struct option options[] = { + OPT__QUIET(&quiet), + OPT_STRING('m', NULL, &msg, "reason", "reason of the update"), + OPT_END(), + }; git_config(git_default_config); - - while (1 < argc) { - const char *arg = argv[1]; - if (arg[0] != '-') - break; - else if (!strcmp("-q", arg)) - quiet = 1; - else if (!strcmp("-m", arg)) { - argc--; - argv++; - if (argc <= 1) - break; - msg = argv[1]; - if (!*msg) - die("Refusing to perform update with empty message"); - } - else if (!strcmp("--", arg)) { - argc--; - argv++; - break; - } - else - die("unknown option %s", arg); - argc--; - argv++; - } - + argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0); + if (msg &&!*msg) + die("Refusing to perform update with empty message"); switch (argc) { - case 2: - check_symref(argv[1], quiet); + case 1: + check_symref(argv[0], quiet); break; - case 3: - create_symref(argv[1], argv[2], msg); + case 2: + create_symref(argv[0], argv[1], msg); break; default: - usage(git_symbolic_ref_usage); + usage_with_options(git_symbolic_ref_usage, options); } return 0; } diff --git a/builtin-tag.c b/builtin-tag.c index 3a9d2eea71..cbb0f04e85 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -17,12 +17,11 @@ static const char builtin_tag_usage[] = static char signingkey[1000]; -static void launch_editor(const char *path, char **buffer, unsigned long *len) +static void launch_editor(const char *path, struct strbuf *buffer) { const char *editor, *terminal; struct child_process child; const char *args[3]; - int fd; editor = getenv("GIT_EDITOR"); if (!editor && editor_program) @@ -52,15 +51,9 @@ static void launch_editor(const char *path, char **buffer, unsigned long *len) if (run_command(&child)) die("There was a problem with the editor %s.", editor); - fd = open(path, O_RDONLY); - if (fd < 0) - die("could not open '%s': %s", path, strerror(errno)); - if (read_fd(fd, buffer, len)) { - free(*buffer); + if (strbuf_read_file(buffer, path, 0) < 0) die("could not read message file '%s': %s", - path, strerror(errno)); - } - close(fd); + path, strerror(errno)); } struct tag_filter { @@ -88,17 +81,16 @@ static int show_reference(const char *refname, const unsigned char *sha1, } printf("%-15s ", refname); - sp = buf = read_sha1_file(sha1, &type, &size); - if (!buf) + buf = read_sha1_file(sha1, &type, &size); + if (!buf || !size) return 0; - if (!size) { + + /* skip header */ + sp = strstr(buf, "\n\n"); + if (!sp) { free(buf); return 0; } - /* skip header */ - while (sp + 1 < buf + size && - !(sp[0] == '\n' && sp[1] == '\n')) - sp++; /* only take up to "lines" lines, and strip the signature */ for (i = 0, sp += 2; i < filter->lines && sp < buf + size && @@ -184,7 +176,7 @@ static int verify_tag(const char *name, const char *ref, return 0; } -static ssize_t do_sign(char *buffer, size_t size, size_t max) +static int do_sign(struct strbuf *buffer) { struct child_process gpg; const char *args[4]; @@ -216,22 +208,22 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max) if (start_command(&gpg)) return error("could not run gpg."); - if (write_in_full(gpg.in, buffer, size) != size) { + if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { close(gpg.in); finish_command(&gpg); return error("gpg did not accept the tag data"); } close(gpg.in); gpg.close_in = 0; - len = read_in_full(gpg.out, buffer + size, max - size); + len = strbuf_read(buffer, gpg.out, 1024); if (finish_command(&gpg) || !len || len < 0) return error("gpg failed to sign the tag"); - if (len == max - size) + if (len < 0) return error("could not read the entire signature from gpg."); - return size + len; + return 0; } static const char tag_template[] = @@ -254,15 +246,41 @@ static int git_tag_config(const char *var, const char *value) return git_default_config(var, value); } -#define MAX_SIGNATURE_LENGTH 1024 -/* message must be NULL or allocated, it will be reallocated and freed */ +static void write_tag_body(int fd, const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + char *buf, *sp, *eob; + size_t len; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return; + /* skip header */ + sp = strstr(buf, "\n\n"); + + if (!sp || !size || type != OBJ_TAG) { + free(buf); + return; + } + sp += 2; /* skip the 2 LFs */ + eob = strstr(sp, "\n" PGP_SIGNATURE "\n"); + if (eob) + len = eob - sp; + else + len = buf + size - sp; + write_or_die(fd, sp, len); + + free(buf); +} + static void create_tag(const unsigned char *object, const char *tag, - char *message, int sign, unsigned char *result) + struct strbuf *buf, int message, int sign, + unsigned char *prev, unsigned char *result) { enum object_type type; - char header_buf[1024], *buffer = NULL; - int header_len, max_size; - unsigned long size = 0; + char header_buf[1024]; + int header_len; type = sha1_object_info(object, NULL); if (type <= OBJ_NONE) @@ -291,56 +309,44 @@ static void create_tag(const unsigned char *object, const char *tag, if (fd < 0) die("could not create file '%s': %s", path, strerror(errno)); - write_or_die(fd, tag_template, strlen(tag_template)); + + if (!is_null_sha1(prev)) + write_tag_body(fd, prev); + else + write_or_die(fd, tag_template, strlen(tag_template)); close(fd); - launch_editor(path, &buffer, &size); + launch_editor(path, buf); unlink(path); free(path); } - else { - buffer = message; - size = strlen(message); - } - size = stripspace(buffer, size, 1); + stripspace(buf, 1); - if (!message && !size) + if (!message && !buf->len) die("no tag message?"); - /* insert the header and add the '\n' if needed: */ - max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1; - buffer = xrealloc(buffer, max_size); - if (size) - buffer[size++] = '\n'; - memmove(buffer + header_len, buffer, size); - memcpy(buffer, header_buf, header_len); - size += header_len; - - if (sign) { - ssize_t r = do_sign(buffer, size, max_size); - if (r < 0) - die("unable to sign the tag"); - size = r; - } + strbuf_insert(buf, 0, header_buf, header_len); - if (write_sha1_file(buffer, size, tag_type, result) < 0) + if (sign && do_sign(buf) < 0) + die("unable to sign the tag"); + if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) die("unable to write tag file"); - free(buffer); } int cmd_tag(int argc, const char **argv, const char *prefix) { + struct strbuf buf; unsigned char object[20], prev[20]; - int annotate = 0, sign = 0, force = 0, lines = 0; - char *message = NULL; + int annotate = 0, sign = 0, force = 0, lines = 0, message = 0; char ref[PATH_MAX]; const char *object_ref, *tag; int i; struct ref_lock *lock; git_config(git_tag_config); + strbuf_init(&buf, 0); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -376,13 +382,11 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die("option -m needs an argument."); if (message) die("only one -F or -m option is allowed."); - message = xstrdup(argv[i]); + strbuf_addstr(&buf, argv[i]); + message = 1; continue; } if (!strcmp(arg, "-F")) { - unsigned long len; - int fd; - annotate = 1; i++; if (i == argc) @@ -390,20 +394,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (message) die("only one -F or -m option is allowed."); - if (!strcmp(argv[i], "-")) - fd = 0; - else { - fd = open(argv[i], O_RDONLY); - if (fd < 0) - die("could not open '%s': %s", + if (!strcmp(argv[i], "-")) { + if (strbuf_read(&buf, 0, 1024) < 0) + die("cannot read %s", argv[i]); + } else { + if (strbuf_read_file(&buf, argv[i], 1024) < 0) + die("could not open or read '%s': %s", argv[i], strerror(errno)); } - len = 1024; - message = xmalloc(len); - if (read_fd(fd, &message, &len)) { - free(message); - die("cannot read %s", argv[i]); - } + message = 1; continue; } if (!strcmp(arg, "-u")) { @@ -451,7 +450,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die("tag '%s' already exists", tag); if (annotate) - create_tag(object, tag, message, sign, object); + create_tag(object, tag, &buf, message, sign, prev, object); lock = lock_any_ref_for_update(ref, prev, 0); if (!lock) @@ -459,5 +458,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (write_ref_sha1(lock, object, NULL) < 0) die("%s: cannot update the ref", ref); + strbuf_release(&buf); return 0; } diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index a6ff62fd8c..1e51865c52 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -311,7 +311,7 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress progress; + struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); unsigned nr_objects = ntohl(hdr->hdr_entries); @@ -322,15 +322,13 @@ static void unpack_all(void) use(sizeof(struct pack_header)); if (!quiet) - start_progress(&progress, "Unpacking %u objects...", "", nr_objects); + progress = start_progress("Unpacking objects", nr_objects); obj_list = xmalloc(nr_objects * sizeof(*obj_list)); for (i = 0; i < nr_objects; i++) { unpack_one(i); - if (!quiet) - display_progress(&progress, i + 1); + display_progress(progress, i + 1); } - if (!quiet) - stop_progress(&progress); + stop_progress(&progress); if (delta_list) die("unresolved deltas left after unpacking"); diff --git a/builtin-update-index.c b/builtin-update-index.c index a7a4574f2b..e1a938d897 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -4,7 +4,6 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" -#include "strbuf.h" #include "quote.h" #include "cache-tree.h" #include "tree-walk.h" @@ -195,11 +194,6 @@ static int process_path(const char *path) int len; struct stat st; - /* We probably want to do this in remove_file_from_cache() and - * add_cache_entry() instead... - */ - cache_tree_invalidate_path(active_cache_tree, path); - /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -239,7 +233,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return error("%s: cannot add to the index - missing --add option?", path); report("add '%s'", path); - cache_tree_invalidate_path(active_cache_tree, path); return 0; } @@ -284,7 +277,6 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } - cache_tree_invalidate_path(active_cache_tree, path); if (force_remove) { if (remove_file_from_cache(p)) @@ -303,8 +295,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length) static void read_index_info(int line_termination) { struct strbuf buf; - strbuf_init(&buf); - while (1) { + struct strbuf uq; + + strbuf_init(&buf, 0); + strbuf_init(&uq, 0); + while (strbuf_getline(&buf, stdin, line_termination) != EOF) { char *ptr, *tab; char *path_name; unsigned char sha1[20]; @@ -328,10 +323,6 @@ static void read_index_info(int line_termination) * This format is to put higher order stages into the * index file and matches git-ls-files --stage output. */ - read_line(&buf, stdin, line_termination); - if (buf.eof) - break; - errno = 0; ul = strtoul(buf.buf, &ptr, 8); if (ptr == buf.buf || *ptr != ' ' @@ -356,18 +347,19 @@ static void read_index_info(int line_termination) if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ') goto bad_line; - if (line_termination && ptr[0] == '"') - path_name = unquote_c_style(ptr, NULL); - else - path_name = ptr; + path_name = ptr; + if (line_termination && path_name[0] == '"') { + strbuf_reset(&uq); + if (unquote_c_style(&uq, path_name, NULL)) { + die("git-update-index: bad quoting of path name"); + } + path_name = uq.buf; + } if (!verify_path(path_name)) { fprintf(stderr, "Ignoring path %s\n", path_name); - if (path_name != ptr) - free(path_name); continue; } - cache_tree_invalidate_path(active_cache_tree, path_name); if (!mode) { /* mode == 0 means there is no such path -- remove */ @@ -385,13 +377,13 @@ static void read_index_info(int line_termination) die("git-update-index: unable to update %s", path_name); } - if (path_name != ptr) - free(path_name); continue; bad_line: die("malformed index info %s", buf.buf); } + strbuf_release(&buf); + strbuf_release(&uq); } static const char update_index_usage[] = @@ -474,7 +466,6 @@ static int unresolve_one(const char *path) goto free_return; } - cache_tree_invalidate_path(active_cache_tree, path); remove_file_from_cache(path); if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { error("%s: cannot add our version to the index.", path); @@ -715,27 +706,27 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) free((char*)p); } if (read_from_stdin) { - struct strbuf buf; - strbuf_init(&buf); - while (1) { - char *path_name; + struct strbuf buf, nbuf; + + strbuf_init(&buf, 0); + strbuf_init(&nbuf, 0); + while (strbuf_getline(&buf, stdin, line_termination) != EOF) { const char *p; - read_line(&buf, stdin, line_termination); - if (buf.eof) - break; - if (line_termination && buf.buf[0] == '"') - path_name = unquote_c_style(buf.buf, NULL); - else - path_name = buf.buf; - p = prefix_path(prefix, prefix_length, path_name); + if (line_termination && buf.buf[0] == '"') { + strbuf_reset(&nbuf); + if (unquote_c_style(&nbuf, buf.buf, NULL)) + die("line is badly quoted"); + strbuf_swap(&buf, &nbuf); + } + p = prefix_path(prefix, prefix_length, buf.buf); update_one(p, NULL, 0); if (set_executable_bit) chmod_path(set_executable_bit, p); - if (p < path_name || p > path_name + strlen(path_name)) - free((char*) p); - if (path_name != buf.buf) - free(path_name); + if (p < buf.buf || p > buf.buf + buf.len) + free((char *)p); } + strbuf_release(&nbuf); + strbuf_release(&buf); } finish: diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 8339cf19e2..e90737c350 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -1,60 +1,44 @@ #include "cache.h" #include "refs.h" #include "builtin.h" +#include "parse-options.h" -static const char git_update_ref_usage[] = -"git-update-ref [-m <reason>] (-d <refname> <value> | [--no-deref] <refname> <value> [<oldval>])"; +static const char * const git_update_ref_usage[] = { + "git-update-ref [options] -d <refname> <oldval>", + "git-update-ref [options] <refname> <newval> [<oldval>]", + NULL +}; int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; - struct ref_lock *lock; + const char *refname, *value, *oldval, *msg=NULL; unsigned char sha1[20], oldsha1[20]; - int i, delete, ref_flags; + int delete = 0, no_deref = 0; + struct option options[] = { + OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"), + OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"), + OPT_BOOLEAN( 0 , "no-deref", &no_deref, + "update <refname> not the one it points to"), + OPT_END(), + }; - delete = 0; - ref_flags = 0; git_config(git_default_config); + argc = parse_options(argc, argv, options, git_update_ref_usage, 0); + if (msg && !*msg) + die("Refusing to perform update with empty message."); - for (i = 1; i < argc; i++) { - if (!strcmp("-m", argv[i])) { - if (i+1 >= argc) - usage(git_update_ref_usage); - msg = argv[++i]; - if (!*msg) - die("Refusing to perform update with empty message."); - continue; - } - if (!strcmp("-d", argv[i])) { - delete = 1; - continue; - } - if (!strcmp("--no-deref", argv[i])) { - ref_flags |= REF_NODEREF; - continue; - } - if (!refname) { - refname = argv[i]; - continue; - } - if (!value) { - value = argv[i]; - continue; - } - if (!oldval) { - oldval = argv[i]; - continue; - } - } - if (!refname || !value) - usage(git_update_ref_usage); + if (argc < 2 || argc > 3) + usage_with_options(git_update_ref_usage, options); + refname = argv[0]; + value = argv[1]; + oldval = argv[2]; if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); if (delete) { if (oldval) - usage(git_update_ref_usage); + usage_with_options(git_update_ref_usage, options); return delete_ref(refname, sha1); } @@ -62,10 +46,6 @@ 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); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, ref_flags); - if (!lock) - die("%s: cannot lock the ref", refname); - if (write_ref_sha1(lock, sha1, msg) < 0) - die("%s: cannot update the ref", refname); - return 0; + return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, + no_deref ? REF_NODEREF : 0, DIE_ON_ERR); } diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c index dfcfcd0455..cc4c55d7ee 100644 --- a/builtin-verify-tag.c +++ b/builtin-verify-tag.c @@ -35,7 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) /* find the length without signature */ len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) { + while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { eol = memchr(buf + len, '\n', size - len); len += eol ? eol - (buf + len) + 1 : size - len; } @@ -6,8 +6,8 @@ extern const char git_version_string[]; extern const char git_usage_string[]; +extern void list_common_cmds_help(void); extern void help_unknown_cmd(const char *cmd); -extern size_t stripspace(char *buffer, size_t length, int skip_comments); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); @@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); +extern int cmd_clean(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_describe(int argc, const char **argv, const char *prefix); @@ -31,6 +32,8 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_fetch(int argc, const char **argv, const char *prefix); +extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); @@ -40,14 +43,17 @@ extern int cmd_gc(int argc, const char **argv, const char *prefix); extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); extern int cmd_grep(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); +extern int cmd_http_fetch(int argc, const char **argv, const char *prefix); extern int cmd_init_db(int argc, const char **argv, const char *prefix); extern int cmd_log(int argc, const char **argv, const char *prefix); extern int cmd_log_reflog(int argc, const char **argv, const char *prefix); extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_ls_remote(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix); +extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); @@ -60,11 +66,13 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); +extern int cmd_reset(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_runstatus(int argc, const char **argv, const char *prefix); +extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); diff --git a/bundle.c b/bundle.c new file mode 100644 index 0000000000..9b9b9166df --- /dev/null +++ b/bundle.c @@ -0,0 +1,351 @@ +#include "cache.h" +#include "bundle.h" +#include "object.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "list-objects.h" +#include "run-command.h" +#include "refs.h" + +static const char bundle_signature[] = "# v2 git bundle\n"; + +static void add_to_ref_list(const unsigned char *sha1, const char *name, + struct ref_list *list) +{ + if (list->nr + 1 >= list->alloc) { + list->alloc = alloc_nr(list->nr + 1); + list->list = xrealloc(list->list, + list->alloc * sizeof(list->list[0])); + } + memcpy(list->list[list->nr].sha1, sha1, 20); + list->list[list->nr].name = xstrdup(name); + list->nr++; +} + +/* returns an fd */ +int read_bundle_header(const char *path, struct bundle_header *header) +{ + char buffer[1024]; + int fd; + long fpos; + FILE *ffd = fopen(path, "rb"); + + if (!ffd) + return error("could not open '%s'", path); + if (!fgets(buffer, sizeof(buffer), ffd) || + strcmp(buffer, bundle_signature)) { + fclose(ffd); + return error("'%s' does not look like a v2 bundle file", path); + } + while (fgets(buffer, sizeof(buffer), ffd) + && buffer[0] != '\n') { + int is_prereq = buffer[0] == '-'; + int offset = is_prereq ? 1 : 0; + int len = strlen(buffer); + unsigned char sha1[20]; + struct ref_list *list = is_prereq ? &header->prerequisites + : &header->references; + char delim; + + if (buffer[len - 1] == '\n') + buffer[len - 1] = '\0'; + if (get_sha1_hex(buffer + offset, sha1)) { + warning("unrecognized header: %s", buffer); + continue; + } + delim = buffer[40 + offset]; + if (!isspace(delim) && (delim != '\0' || !is_prereq)) + die ("invalid header: %s", buffer); + add_to_ref_list(sha1, isspace(delim) ? + buffer + 41 + offset : "", list); + } + fpos = ftell(ffd); + fclose(ffd); + fd = open(path, O_RDONLY); + if (fd < 0) + return error("could not open '%s'", path); + lseek(fd, fpos, SEEK_SET); + return fd; +} + +static int list_refs(struct ref_list *r, int argc, const char **argv) +{ + int i; + + for (i = 0; i < r->nr; i++) { + if (argc > 1) { + int j; + for (j = 1; j < argc; j++) + if (!strcmp(r->list[i].name, argv[j])) + break; + if (j == argc) + continue; + } + printf("%s %s\n", sha1_to_hex(r->list[i].sha1), + r->list[i].name); + } + return 0; +} + +#define PREREQ_MARK (1u<<16) + +int verify_bundle(struct bundle_header *header, int verbose) +{ + /* + * Do fast check, then if any prereqs are missing then go line by line + * to be verbose about the errors + */ + struct ref_list *p = &header->prerequisites; + struct rev_info revs; + const char *argv[] = {NULL, "--all"}; + struct object_array refs; + struct commit *commit; + int i, ret = 0, req_nr; + const char *message = "Repository lacks these prerequisite commits:"; + + init_revisions(&revs, NULL); + for (i = 0; i < p->nr; i++) { + struct ref_list_entry *e = p->list + i; + struct object *o = parse_object(e->sha1); + if (o) { + o->flags |= PREREQ_MARK; + add_pending_object(&revs, o, e->name); + continue; + } + if (++ret == 1) + error(message); + error("%s %s", sha1_to_hex(e->sha1), e->name); + } + if (revs.pending.nr != p->nr) + return ret; + req_nr = revs.pending.nr; + setup_revisions(2, argv, &revs, NULL); + + memset(&refs, 0, sizeof(struct object_array)); + for (i = 0; i < revs.pending.nr; i++) { + struct object_array_entry *e = revs.pending.objects + i; + add_object_array(e->item, e->name, &refs); + } + + prepare_revision_walk(&revs); + + i = req_nr; + while (i && (commit = get_revision(&revs))) + if (commit->object.flags & PREREQ_MARK) + i--; + + for (i = 0; i < req_nr; i++) + if (!(refs.objects[i].item->flags & SHOWN)) { + if (++ret == 1) + error(message); + error("%s %s", sha1_to_hex(refs.objects[i].item->sha1), + refs.objects[i].name); + } + + for (i = 0; i < refs.nr; i++) + clear_commit_marks((struct commit *)refs.objects[i].item, -1); + + if (verbose) { + struct ref_list *r; + + r = &header->references; + printf("The bundle contains %d ref%s\n", + r->nr, (1 < r->nr) ? "s" : ""); + list_refs(r, 0, NULL); + r = &header->prerequisites; + printf("The bundle requires these %d ref%s\n", + r->nr, (1 < r->nr) ? "s" : ""); + list_refs(r, 0, NULL); + } + return ret; +} + +int list_bundle_refs(struct bundle_header *header, int argc, const char **argv) +{ + return list_refs(&header->references, argc, argv); +} + +int create_bundle(struct bundle_header *header, const char *path, + int argc, const char **argv) +{ + static struct lock_file lock; + int bundle_fd = -1; + int bundle_to_stdout; + const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); + const char **argv_pack = xmalloc(5 * sizeof(const char *)); + int i, ref_count = 0; + char buffer[1024]; + struct rev_info revs; + struct child_process rls; + FILE *rls_fout; + + bundle_to_stdout = !strcmp(path, "-"); + if (bundle_to_stdout) + bundle_fd = 1; + else + bundle_fd = hold_lock_file_for_update(&lock, path, 1); + + /* write signature */ + write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); + + /* init revs to list objects for pack-objects later */ + save_commit_buffer = 0; + init_revisions(&revs, NULL); + + /* write prerequisites */ + memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *)); + argv_boundary[0] = "rev-list"; + argv_boundary[1] = "--boundary"; + argv_boundary[2] = "--pretty=oneline"; + argv_boundary[argc + 2] = NULL; + memset(&rls, 0, sizeof(rls)); + rls.argv = argv_boundary; + rls.out = -1; + rls.git_cmd = 1; + if (start_command(&rls)) + return -1; + rls_fout = fdopen(rls.out, "r"); + while (fgets(buffer, sizeof(buffer), rls_fout)) { + unsigned char sha1[20]; + if (buffer[0] == '-') { + write_or_die(bundle_fd, buffer, strlen(buffer)); + if (!get_sha1_hex(buffer + 1, sha1)) { + struct object *object = parse_object(sha1); + object->flags |= UNINTERESTING; + add_pending_object(&revs, object, buffer); + } + } else if (!get_sha1_hex(buffer, sha1)) { + struct object *object = parse_object(sha1); + object->flags |= SHOWN; + } + } + fclose(rls_fout); + if (finish_command(&rls)) + return error("rev-list died"); + + /* write references */ + argc = setup_revisions(argc, argv, &revs, NULL); + if (argc > 1) + return error("unrecognized argument: %s'", argv[1]); + + for (i = 0; i < revs.pending.nr; i++) { + struct object_array_entry *e = revs.pending.objects + i; + unsigned char sha1[20]; + char *ref; + const char *display_ref; + int flag; + + if (e->item->flags & UNINTERESTING) + continue; + if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) + continue; + if (!resolve_ref(e->name, sha1, 1, &flag)) + flag = 0; + display_ref = (flag & REF_ISSYMREF) ? e->name : ref; + + /* + * Make sure the refs we wrote out is correct; --max-count and + * other limiting options could have prevented all the tips + * from getting output. + * + * Non commit objects such as tags and blobs do not have + * this issue as they are not affected by those extra + * constraints. + */ + if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) { + warning("ref '%s' is excluded by the rev-list options", + e->name); + free(ref); + continue; + } + /* + * If you run "git bundle create bndl v1.0..v2.0", the + * name of the positive ref is "v2.0" but that is the + * commit that is referenced by the tag, and not the tag + * itself. + */ + if (hashcmp(sha1, e->item->sha1)) { + /* + * Is this the positive end of a range expressed + * in terms of a tag (e.g. v2.0 from the range + * "v1.0..v2.0")? + */ + struct commit *one = lookup_commit_reference(sha1); + struct object *obj; + + if (e->item == &(one->object)) { + /* + * Need to include e->name as an + * independent ref to the pack-objects + * input, so that the tag is included + * in the output; otherwise we would + * end up triggering "empty bundle" + * error. + */ + obj = parse_object(sha1); + obj->flags |= SHOWN; + add_pending_object(&revs, obj, e->name); + } + free(ref); + continue; + } + + ref_count++; + write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); + write_or_die(bundle_fd, " ", 1); + write_or_die(bundle_fd, display_ref, strlen(display_ref)); + write_or_die(bundle_fd, "\n", 1); + free(ref); + } + if (!ref_count) + die ("Refusing to create empty bundle."); + + /* end header */ + write_or_die(bundle_fd, "\n", 1); + + /* write pack */ + argv_pack[0] = "pack-objects"; + argv_pack[1] = "--all-progress"; + argv_pack[2] = "--stdout"; + argv_pack[3] = "--thin"; + argv_pack[4] = NULL; + memset(&rls, 0, sizeof(rls)); + rls.argv = argv_pack; + rls.in = -1; + rls.out = bundle_fd; + rls.git_cmd = 1; + if (start_command(&rls)) + return error("Could not spawn pack-objects"); + for (i = 0; i < revs.pending.nr; i++) { + struct object *object = revs.pending.objects[i].item; + if (object->flags & UNINTERESTING) + write(rls.in, "^", 1); + write(rls.in, sha1_to_hex(object->sha1), 40); + write(rls.in, "\n", 1); + } + if (finish_command(&rls)) + return error ("pack-objects died"); + close(bundle_fd); + if (!bundle_to_stdout) + commit_lock_file(&lock); + return 0; +} + +int unbundle(struct bundle_header *header, int bundle_fd) +{ + const char *argv_index_pack[] = {"index-pack", + "--fix-thin", "--stdin", NULL}; + struct child_process ip; + + if (verify_bundle(header, 0)) + return -1; + memset(&ip, 0, sizeof(ip)); + ip.argv = argv_index_pack; + ip.in = bundle_fd; + ip.no_stdout = 1; + ip.git_cmd = 1; + if (run_command(&ip)) + return error("index-pack died"); + return 0; +} diff --git a/bundle.h b/bundle.h new file mode 100644 index 0000000000..e2aedd60d6 --- /dev/null +++ b/bundle.h @@ -0,0 +1,25 @@ +#ifndef BUNDLE_H +#define BUNDLE_H + +struct ref_list { + unsigned int nr, alloc; + struct ref_list_entry { + unsigned char sha1[20]; + char *name; + } *list; +}; + +struct bundle_header { + struct ref_list prerequisites; + struct ref_list references; +}; + +int read_bundle_header(const char *path, struct bundle_header *header); +int create_bundle(struct bundle_header *header, const char *path, + int argc, const char **argv); +int verify_bundle(struct bundle_header *header, int verbose); +int unbundle(struct bundle_header *header, int bundle_fd); +int list_bundle_refs(struct bundle_header *header, + int argc, const char **argv); + +#endif diff --git a/cache-tree.c b/cache-tree.c index 077f034369..50b35264fd 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -235,8 +235,7 @@ static int update_one(struct cache_tree *it, int missing_ok, int dryrun) { - unsigned long size, offset; - char *buffer; + struct strbuf buffer; int i; if (0 <= it->entry_count && has_sha1_file(it->sha1)) @@ -293,9 +292,7 @@ static int update_one(struct cache_tree *it, /* * Then write out the tree object for this level. */ - size = 8192; - buffer = xmalloc(size); - offset = 0; + strbuf_init(&buffer, 8192); for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; @@ -332,15 +329,9 @@ static int update_one(struct cache_tree *it, if (!ce->ce_mode) continue; /* entry being removed */ - if (size < offset + entlen + 100) { - size = alloc_nr(offset + entlen + 100); - buffer = xrealloc(buffer, size); - } - offset += sprintf(buffer + offset, - "%o %.*s", mode, entlen, path + baselen); - buffer[offset++] = 0; - hashcpy((unsigned char*)buffer + offset, sha1); - offset += 20; + strbuf_grow(&buffer, entlen + 100); + strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0'); + strbuf_add(&buffer, sha1, 20); #if DEBUG fprintf(stderr, "cache-tree update-one %o %.*s\n", @@ -349,10 +340,10 @@ static int update_one(struct cache_tree *it, } if (dryrun) - hash_sha1_file(buffer, offset, tree_type, it->sha1); + hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); else - write_sha1_file(buffer, offset, tree_type, it->sha1); - free(buffer); + write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); + strbuf_release(&buffer); it->entry_count = i; #if DEBUG fprintf(stderr, "cache-tree update-one (%d ent, %d subtree) %s\n", @@ -378,12 +369,8 @@ int cache_tree_update(struct cache_tree *it, return 0; } -static void *write_one(struct cache_tree *it, - char *path, - int pathlen, - char *buffer, - unsigned long *size, - unsigned long *offset) +static void write_one(struct strbuf *buffer, struct cache_tree *it, + const char *path, int pathlen) { int i; @@ -393,13 +380,9 @@ static void *write_one(struct cache_tree *it, * tree-sha1 (missing if invalid) * subtree_nr "cache-tree" entries for subtrees. */ - if (*size < *offset + pathlen + 100) { - *size = alloc_nr(*offset + pathlen + 100); - buffer = xrealloc(buffer, *size); - } - *offset += sprintf(buffer + *offset, "%.*s%c%d %d\n", - pathlen, path, 0, - it->entry_count, it->subtree_nr); + strbuf_grow(buffer, pathlen + 100); + strbuf_add(buffer, path, pathlen); + strbuf_addf(buffer, "%c%d %d\n", 0, it->entry_count, it->subtree_nr); #if DEBUG if (0 <= it->entry_count) @@ -412,8 +395,7 @@ static void *write_one(struct cache_tree *it, #endif if (0 <= it->entry_count) { - hashcpy((unsigned char*)buffer + *offset, it->sha1); - *offset += 20; + strbuf_add(buffer, it->sha1, 20); } for (i = 0; i < it->subtree_nr; i++) { struct cache_tree_sub *down = it->down[i]; @@ -423,21 +405,13 @@ static void *write_one(struct cache_tree *it, prev->name, prev->namelen) <= 0) die("fatal - unsorted cache subtree"); } - buffer = write_one(down->cache_tree, down->name, down->namelen, - buffer, size, offset); + write_one(buffer, down->cache_tree, down->name, down->namelen); } - return buffer; } -void *cache_tree_write(struct cache_tree *root, unsigned long *size_p) +void cache_tree_write(struct strbuf *sb, struct cache_tree *root) { - char path[PATH_MAX]; - unsigned long size = 8192; - char *buffer = xmalloc(size); - - *size_p = 0; - path[0] = 0; - return write_one(root, path, 0, buffer, &size, size_p); + write_one(sb, root, "", 0); } static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) diff --git a/cache-tree.h b/cache-tree.h index 119407e3a1..8243228e49 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -22,7 +22,7 @@ void cache_tree_free(struct cache_tree **); void cache_tree_invalidate_path(struct cache_tree *, const char *); struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); -void *cache_tree_write(struct cache_tree *root, unsigned long *size_p); +void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); int cache_tree_fully_valid(struct cache_tree *); @@ -2,6 +2,7 @@ #define CACHE_H #include "git-compat-util.h" +#include "strbuf.h" #include SHA1_HEADER #include <zlib.h> @@ -221,6 +222,7 @@ extern const char *get_git_work_tree(void); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" extern const char **get_pathspec(const char *prefix, const char **pathspec); +extern void setup_work_tree(void); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); @@ -276,7 +278,6 @@ extern int ie_modified(struct index_state *, struct cache_entry *, struct stat * extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); -extern int read_fd(int fd, char **return_buf, unsigned long *return_size); extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object); extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); @@ -289,6 +290,7 @@ extern int refresh_index(struct index_state *, unsigned int flags, const char ** struct lock_file { struct lock_file *next; + int fd; pid_t owner; char on_list; char filename[PATH_MAX]; @@ -438,6 +440,7 @@ const char *show_date(unsigned long time, int timezone, enum date_mode mode); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); +enum date_mode parse_date_format(const char *format); extern const char *git_author_info(int); extern const char *git_committer_info(int); @@ -497,7 +500,20 @@ struct ref { struct ref *next; unsigned char old_sha1[20]; unsigned char new_sha1[20]; - unsigned char force; + unsigned int force:1, + merge:1, + nonfastforward:1, + deletion:1; + enum { + REF_STATUS_NONE = 0, + REF_STATUS_OK, + REF_STATUS_REJECT_NONFASTFORWARD, + REF_STATUS_REJECT_NODELETE, + REF_STATUS_UPTODATE, + REF_STATUS_REMOTE_REJECT, + REF_STATUS_EXPECTING_REPORT, + } status; + char *remote_status; struct ref *peer_ref; /* when renaming */ char name[FLEX_ARRAY]; /* more */ }; @@ -506,9 +522,11 @@ struct ref { #define REF_HEADS (1u << 1) #define REF_TAGS (1u << 2) +extern struct ref *find_ref_by_name(struct ref *list, const char *name); + #define CONNECT_VERBOSE (1u << 0) -extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags); -extern int finish_connect(pid_t pid); +extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); +extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); extern int get_ack(int fd, unsigned char *result_sha1); extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); @@ -536,6 +554,7 @@ extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsign extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern int matches_pack_name(struct packed_git *p, const char *name); /* Dumb servers support */ extern int update_server_info(int); @@ -552,6 +571,7 @@ extern int git_config_bool(const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); +extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value); #define MAX_GITNAME (1000) @@ -592,15 +612,13 @@ extern void *alloc_object_node(void); extern void alloc_report(void); /* trace.c */ -extern int nfasprintf(char **str, const char *fmt, ...); -extern int nfvasprintf(char **str, const char *fmt, va_list va); extern void trace_printf(const char *format, ...); extern void trace_argv_printf(const char **argv, int count, const char *format, ...); /* convert.c */ -extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep); -extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep); -extern void *convert_sha1_file(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size); +/* returns 1 if *dst was used */ +extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst); +extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); /* diff.c */ extern int diff_auto_refresh_index; @@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value) return git_config_bool(var, value); } -static int color_vprintf(const char *color, const char *fmt, +static int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args, const char *trail) { int r = 0; if (*color) - r += printf("%s", color); - r += vprintf(fmt, args); + r += fprintf(fp, "%s", color); + r += vfprintf(fp, fmt, args); if (*color) - r += printf("%s", COLOR_RESET); + r += fprintf(fp, "%s", COLOR_RESET); if (trail) - r += printf("%s", trail); + r += fprintf(fp, "%s", trail); return r; } -int color_printf(const char *color, const char *fmt, ...) +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, NULL); + r = color_vfprintf(fp, color, fmt, args, NULL); va_end(args); return r; } -int color_printf_ln(const char *color, const char *fmt, ...) +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, "\n"); + r = color_vfprintf(fp, color, fmt, args, "\n"); va_end(args); return r; } @@ -6,7 +6,7 @@ int git_config_colorbool(const char *var, const char *value); void color_parse(const char *var, const char *value, char *dst); -int color_printf(const char *color, const char *fmt, ...); -int color_printf_ln(const char *color, const char *fmt, ...); +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); #endif /* COLOR_H */ diff --git a/combine-diff.c b/combine-diff.c index ef622340a5..5a658dc0d5 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -650,10 +650,7 @@ static void dump_quoted_path(const char *prefix, const char *path, const char *c_meta, const char *c_reset) { printf("%s%s", c_meta, prefix); - if (quote_c_style(path, NULL, NULL, 0)) - quote_c_style(path, NULL, stdout, 0); - else - printf("%s", path); + quote_c_style(path, NULL, stdout, 0); printf("%s\n", c_reset); } @@ -667,7 +664,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int mode_differs = 0; int i, show_hunks; int working_tree_file = is_null_sha1(elem->sha1); - int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; + int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV; mmfile_t result_file; context = opt->context; @@ -787,7 +784,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, if (show_hunks || mode_differs || working_tree_file) { const char *abb; - int use_color = opt->color_diff; + int use_color = DIFF_OPT_TST(opt, COLOR_DIFF); const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); const char *c_reset = diff_get_color(use_color, DIFF_RESET); int added = 0; @@ -839,7 +836,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, dump_quoted_path("+++ /dev/", "null", c_meta, c_reset); else dump_quoted_path("+++ b/", elem->path, c_meta, c_reset); - dump_sline(sline, cnt, num_parent, opt->color_diff); + dump_sline(sline, cnt, num_parent, DIFF_OPT_TST(opt, COLOR_DIFF)); } free(result); @@ -900,16 +897,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re putchar(inter_name_termination); } - if (line_termination) { - if (quote_c_style(p->path, NULL, NULL, 0)) - quote_c_style(p->path, NULL, stdout, 0); - else - printf("%s", p->path); - putchar(line_termination); - } - else { - printf("%s%c", p->path, line_termination); - } + write_name_quoted(p->path, stdout, line_termination); } void show_combined_diff(struct combine_diff_path *p, @@ -941,8 +929,8 @@ void diff_tree_combined(const unsigned char *sha1, diffopts = *opt; diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; - diffopts.recursive = 1; - diffopts.allow_external = 0; + DIFF_OPT_SET(&diffopts, RECURSIVE); + DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL); show_log_first = !!rev->loginfo && !rev->no_commit_id; needsep = 0; @@ -3,70 +3,13 @@ #include "commit.h" #include "pkt-line.h" #include "utf8.h" -#include "interpolate.h" #include "diff.h" #include "revision.h" int save_commit_buffer = 1; -struct sort_node -{ - /* - * the number of children of the associated commit - * that also occur in the list being sorted. - */ - unsigned int indegree; - - /* - * reference to original list item that we will re-use - * on output. - */ - struct commit_list * list_item; - -}; - const char *commit_type = "commit"; -static struct cmt_fmt_map { - const char *n; - size_t cmp_len; - enum cmit_fmt v; -} cmt_fmts[] = { - { "raw", 1, CMIT_FMT_RAW }, - { "medium", 1, CMIT_FMT_MEDIUM }, - { "short", 1, CMIT_FMT_SHORT }, - { "email", 1, CMIT_FMT_EMAIL }, - { "full", 5, CMIT_FMT_FULL }, - { "fuller", 5, CMIT_FMT_FULLER }, - { "oneline", 1, CMIT_FMT_ONELINE }, - { "format:", 7, CMIT_FMT_USERFORMAT}, -}; - -static char *user_format; - -enum cmit_fmt get_commit_format(const char *arg) -{ - int i; - - if (!arg || !*arg) - return CMIT_FMT_DEFAULT; - if (*arg == '=') - arg++; - if (!prefixcmp(arg, "format:")) { - if (user_format) - free(user_format); - user_format = xstrdup(arg + 7); - return CMIT_FMT_USERFORMAT; - } - for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { - if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) && - !strncmp(arg, cmt_fmts[i].n, strlen(arg))) - return cmt_fmts[i].v; - } - - die("invalid --pretty format: %s", arg); -} - static struct commit *check_commit(struct object *obj, const unsigned char *sha1, int quiet) @@ -460,813 +403,6 @@ void clear_commit_marks(struct commit *commit, unsigned int mark) } } -/* - * Generic support for pretty-printing the header - */ -static int get_one_line(const char *msg, unsigned long len) -{ - int ret = 0; - - while (len--) { - char c = *msg++; - if (!c) - break; - ret++; - if (c == '\n') - break; - } - return ret; -} - -/* High bit set, or ISO-2022-INT */ -int non_ascii(int ch) -{ - ch = (ch & 0xff); - return ((ch & 0x80) || (ch == 0x1b)); -} - -static int is_rfc2047_special(char ch) -{ - return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_')); -} - -static int add_rfc2047(char *buf, const char *line, int len, - const char *encoding) -{ - char *bp = buf; - int i, needquote; - char q_encoding[128]; - const char *q_encoding_fmt = "=?%s?q?"; - - for (i = needquote = 0; !needquote && i < len; i++) { - int ch = line[i]; - if (non_ascii(ch)) - needquote++; - if ((i + 1 < len) && - (ch == '=' && line[i+1] == '?')) - needquote++; - } - if (!needquote) - return sprintf(buf, "%.*s", len, line); - - i = snprintf(q_encoding, sizeof(q_encoding), q_encoding_fmt, encoding); - if (sizeof(q_encoding) < i) - die("Insanely long encoding name %s", encoding); - memcpy(bp, q_encoding, i); - bp += i; - for (i = 0; i < len; i++) { - unsigned ch = line[i] & 0xFF; - /* - * We encode ' ' using '=20' even though rfc2047 - * allows using '_' for readability. Unfortunately, - * many programs do not understand this and just - * leave the underscore in place. - */ - if (is_rfc2047_special(ch) || ch == ' ') { - sprintf(bp, "=%02X", ch); - bp += 3; - } - else - *bp++ = ch; - } - memcpy(bp, "?=", 2); - bp += 2; - return bp - buf; -} - -static unsigned long bound_rfc2047(unsigned long len, const char *encoding) -{ - /* upper bound of q encoded string of length 'len' */ - unsigned long elen = strlen(encoding); - - return len * 3 + elen + 100; -} - -static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, - const char *line, enum date_mode dmode, - const char *encoding) -{ - char *date; - int namelen; - unsigned long time; - int tz, ret; - const char *filler = " "; - - if (fmt == CMIT_FMT_ONELINE) - return 0; - date = strchr(line, '>'); - if (!date) - return 0; - namelen = ++date - line; - time = strtoul(date, &date, 10); - tz = strtol(date, NULL, 10); - - if (fmt == CMIT_FMT_EMAIL) { - char *name_tail = strchr(line, '<'); - int display_name_length; - if (!name_tail) - return 0; - while (line < name_tail && isspace(name_tail[-1])) - name_tail--; - display_name_length = name_tail - line; - filler = ""; - strcpy(buf, "From: "); - ret = strlen(buf); - ret += add_rfc2047(buf + ret, line, display_name_length, - encoding); - memcpy(buf + ret, name_tail, namelen - display_name_length); - ret += namelen - display_name_length; - buf[ret++] = '\n'; - } - else { - ret = sprintf(buf, "%s: %.*s%.*s\n", what, - (fmt == CMIT_FMT_FULLER) ? 4 : 0, - filler, namelen, line); - } - switch (fmt) { - case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", - show_date(time, tz, dmode)); - break; - case CMIT_FMT_EMAIL: - ret += sprintf(buf + ret, "Date: %s\n", - show_date(time, tz, DATE_RFC2822)); - break; - case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, - show_date(time, tz, dmode)); - break; - default: - /* notin' */ - break; - } - return ret; -} - -static int is_empty_line(const char *line, int *len_p) -{ - int len = *len_p; - while (len && isspace(line[len-1])) - len--; - *len_p = len; - return !len; -} - -static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev) -{ - struct commit_list *parent = commit->parents; - int offset; - - if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || - !parent || !parent->next) - return 0; - - offset = sprintf(buf, "Merge:"); - - while (parent) { - struct commit *p = parent->item; - const char *hex = NULL; - const char *dots; - if (abbrev) - hex = find_unique_abbrev(p->object.sha1, abbrev); - if (!hex) - hex = sha1_to_hex(p->object.sha1); - dots = (abbrev && strlen(hex) != 40) ? "..." : ""; - parent = parent->next; - - offset += sprintf(buf + offset, " %s%s", hex, dots); - } - buf[offset++] = '\n'; - return offset; -} - -static char *get_header(const struct commit *commit, const char *key) -{ - int key_len = strlen(key); - const char *line = commit->buffer; - - for (;;) { - const char *eol = strchr(line, '\n'), *next; - - if (line == eol) - return NULL; - if (!eol) { - eol = line + strlen(line); - next = NULL; - } else - next = eol + 1; - if (eol - line > key_len && - !strncmp(line, key, key_len) && - line[key_len] == ' ') { - int len = eol - line - key_len; - char *ret = xmalloc(len); - memcpy(ret, line + key_len + 1, len - 1); - ret[len - 1] = '\0'; - return ret; - } - line = next; - } -} - -static char *replace_encoding_header(char *buf, const char *encoding) -{ - char *encoding_header = strstr(buf, "\nencoding "); - char *header_end = strstr(buf, "\n\n"); - char *end_of_encoding_header; - int encoding_header_pos; - int encoding_header_len; - int new_len; - int need_len; - int buflen = strlen(buf) + 1; - - if (!header_end) - header_end = buf + buflen; - if (!encoding_header || encoding_header >= header_end) - return buf; - encoding_header++; - end_of_encoding_header = strchr(encoding_header, '\n'); - if (!end_of_encoding_header) - return buf; /* should not happen but be defensive */ - end_of_encoding_header++; - - encoding_header_len = end_of_encoding_header - encoding_header; - encoding_header_pos = encoding_header - buf; - - if (is_encoding_utf8(encoding)) { - /* we have re-coded to UTF-8; drop the header */ - memmove(encoding_header, end_of_encoding_header, - buflen - (encoding_header_pos + encoding_header_len)); - return buf; - } - new_len = strlen(encoding); - need_len = new_len + strlen("encoding \n"); - if (encoding_header_len < need_len) { - buf = xrealloc(buf, buflen + (need_len - encoding_header_len)); - encoding_header = buf + encoding_header_pos; - end_of_encoding_header = encoding_header + encoding_header_len; - } - memmove(end_of_encoding_header + (need_len - encoding_header_len), - end_of_encoding_header, - buflen - (encoding_header_pos + encoding_header_len)); - memcpy(encoding_header + 9, encoding, strlen(encoding)); - encoding_header[9 + new_len] = '\n'; - return buf; -} - -static char *logmsg_reencode(const struct commit *commit, - const char *output_encoding) -{ - static const char *utf8 = "utf-8"; - const char *use_encoding; - char *encoding; - char *out; - - if (!*output_encoding) - return NULL; - encoding = get_header(commit, "encoding"); - use_encoding = encoding ? encoding : utf8; - if (!strcmp(use_encoding, output_encoding)) - if (encoding) /* we'll strip encoding header later */ - out = xstrdup(commit->buffer); - else - return NULL; /* nothing to do */ - else - out = reencode_string(commit->buffer, - output_encoding, use_encoding); - if (out) - out = replace_encoding_header(out, output_encoding); - - free(encoding); - return out; -} - -static void fill_person(struct interp *table, const char *msg, int len) -{ - int start, end, tz = 0; - unsigned long date; - char *ep; - - /* parse name */ - for (end = 0; end < len && msg[end] != '<'; end++) - ; /* do nothing */ - start = end + 1; - while (end > 0 && isspace(msg[end - 1])) - end--; - table[0].value = xstrndup(msg, end); - - if (start >= len) - return; - - /* parse email */ - for (end = start + 1; end < len && msg[end] != '>'; end++) - ; /* do nothing */ - - if (end >= len) - return; - - table[1].value = xstrndup(msg + start, end - start); - - /* parse date */ - for (start = end + 1; start < len && isspace(msg[start]); start++) - ; /* do nothing */ - if (start >= len) - return; - date = strtoul(msg + start, &ep, 10); - if (msg + start == ep) - return; - - table[5].value = xstrndup(msg + start, ep - (msg + start)); - - /* parse tz */ - for (start = ep - msg + 1; start < len && isspace(msg[start]); start++) - ; /* do nothing */ - if (start + 1 < len) { - tz = strtoul(msg + start + 1, NULL, 10); - if (msg[start] == '-') - tz = -tz; - } - - interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL)); - interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822)); - interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE)); - interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601)); -} - -static long format_commit_message(const struct commit *commit, - const char *msg, char **buf_p, unsigned long *space_p) -{ - struct interp table[] = { - { "%H" }, /* commit hash */ - { "%h" }, /* abbreviated commit hash */ - { "%T" }, /* tree hash */ - { "%t" }, /* abbreviated tree hash */ - { "%P" }, /* parent hashes */ - { "%p" }, /* abbreviated parent hashes */ - { "%an" }, /* author name */ - { "%ae" }, /* author email */ - { "%ad" }, /* author date */ - { "%aD" }, /* author date, RFC2822 style */ - { "%ar" }, /* author date, relative */ - { "%at" }, /* author date, UNIX timestamp */ - { "%ai" }, /* author date, ISO 8601 */ - { "%cn" }, /* committer name */ - { "%ce" }, /* committer email */ - { "%cd" }, /* committer date */ - { "%cD" }, /* committer date, RFC2822 style */ - { "%cr" }, /* committer date, relative */ - { "%ct" }, /* committer date, UNIX timestamp */ - { "%ci" }, /* committer date, ISO 8601 */ - { "%e" }, /* encoding */ - { "%s" }, /* subject */ - { "%b" }, /* body */ - { "%Cred" }, /* red */ - { "%Cgreen" }, /* green */ - { "%Cblue" }, /* blue */ - { "%Creset" }, /* reset color */ - { "%n" }, /* newline */ - { "%m" }, /* left/right/bottom */ - }; - enum interp_index { - IHASH = 0, IHASH_ABBREV, - ITREE, ITREE_ABBREV, - IPARENTS, IPARENTS_ABBREV, - IAUTHOR_NAME, IAUTHOR_EMAIL, - IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE, - IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601, - ICOMMITTER_NAME, ICOMMITTER_EMAIL, - ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822, - ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP, - ICOMMITTER_ISO8601, - IENCODING, - ISUBJECT, - IBODY, - IRED, IGREEN, IBLUE, IRESET_COLOR, - INEWLINE, - ILEFT_RIGHT, - }; - struct commit_list *p; - char parents[1024]; - int i; - enum { HEADER, SUBJECT, BODY } state; - - if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table)) - die("invalid interp table!"); - - /* these are independent of the commit */ - interp_set_entry(table, IRED, "\033[31m"); - interp_set_entry(table, IGREEN, "\033[32m"); - interp_set_entry(table, IBLUE, "\033[34m"); - interp_set_entry(table, IRESET_COLOR, "\033[m"); - interp_set_entry(table, INEWLINE, "\n"); - - /* these depend on the commit */ - if (!commit->object.parsed) - parse_object(commit->object.sha1); - interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1)); - interp_set_entry(table, IHASH_ABBREV, - find_unique_abbrev(commit->object.sha1, - DEFAULT_ABBREV)); - interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1)); - interp_set_entry(table, ITREE_ABBREV, - find_unique_abbrev(commit->tree->object.sha1, - DEFAULT_ABBREV)); - interp_set_entry(table, ILEFT_RIGHT, - (commit->object.flags & BOUNDARY) - ? "-" - : (commit->object.flags & SYMMETRIC_LEFT) - ? "<" - : ">"); - - parents[1] = 0; - for (i = 0, p = commit->parents; - p && i < sizeof(parents) - 1; - p = p->next) - i += snprintf(parents + i, sizeof(parents) - i - 1, " %s", - sha1_to_hex(p->item->object.sha1)); - interp_set_entry(table, IPARENTS, parents + 1); - - parents[1] = 0; - for (i = 0, p = commit->parents; - p && i < sizeof(parents) - 1; - p = p->next) - i += snprintf(parents + i, sizeof(parents) - i - 1, " %s", - find_unique_abbrev(p->item->object.sha1, - DEFAULT_ABBREV)); - interp_set_entry(table, IPARENTS_ABBREV, parents + 1); - - for (i = 0, state = HEADER; msg[i] && state < BODY; i++) { - int eol; - for (eol = i; msg[eol] && msg[eol] != '\n'; eol++) - ; /* do nothing */ - - if (state == SUBJECT) { - table[ISUBJECT].value = xstrndup(msg + i, eol - i); - i = eol; - } - if (i == eol) { - state++; - /* strip empty lines */ - while (msg[eol + 1] == '\n') - eol++; - } else if (!prefixcmp(msg + i, "author ")) - fill_person(table + IAUTHOR_NAME, - msg + i + 7, eol - i - 7); - else if (!prefixcmp(msg + i, "committer ")) - fill_person(table + ICOMMITTER_NAME, - msg + i + 10, eol - i - 10); - else if (!prefixcmp(msg + i, "encoding ")) - table[IENCODING].value = - xstrndup(msg + i + 9, eol - i - 9); - i = eol; - } - if (msg[i]) - table[IBODY].value = xstrdup(msg + i); - for (i = 0; i < ARRAY_SIZE(table); i++) - if (!table[i].value) - interp_set_entry(table, i, "<unknown>"); - - do { - char *buf = *buf_p; - unsigned long space = *space_p; - - space = interpolate(buf, space, user_format, - table, ARRAY_SIZE(table)); - if (!space) - break; - buf = xrealloc(buf, space); - *buf_p = buf; - *space_p = space; - } while (1); - interp_clear_table(table, ARRAY_SIZE(table)); - - return strlen(*buf_p); -} - -static void pp_header(enum cmit_fmt fmt, - int abbrev, - enum date_mode dmode, - const char *encoding, - const struct commit *commit, - const char **msg_p, - unsigned long *len_p, - unsigned long *ofs_p, - char **buf_p, - unsigned long *space_p) -{ - int parents_shown = 0; - - for (;;) { - const char *line = *msg_p; - char *dst; - int linelen = get_one_line(*msg_p, *len_p); - unsigned long len; - - if (!linelen) - return; - *msg_p += linelen; - *len_p -= linelen; - - if (linelen == 1) - /* End of header */ - return; - - ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p); - dst = *buf_p + *ofs_p; - - if (fmt == CMIT_FMT_RAW) { - memcpy(dst, line, linelen); - *ofs_p += linelen; - continue; - } - - if (!memcmp(line, "parent ", 7)) { - if (linelen != 48) - die("bad parent line in commit"); - continue; - } - - if (!parents_shown) { - struct commit_list *parent; - int num; - for (parent = commit->parents, num = 0; - parent; - parent = parent->next, num++) - ; - /* with enough slop */ - num = *ofs_p + num * 50 + 20; - ALLOC_GROW(*buf_p, num, *space_p); - dst = *buf_p + *ofs_p; - *ofs_p += add_merge_info(fmt, dst, commit, abbrev); - parents_shown = 1; - } - - /* - * MEDIUM == DEFAULT shows only author with dates. - * FULL shows both authors but not dates. - * FULLER shows both authors and dates. - */ - if (!memcmp(line, "author ", 7)) { - len = linelen; - if (fmt == CMIT_FMT_EMAIL) - len = bound_rfc2047(linelen, encoding); - ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p); - dst = *buf_p + *ofs_p; - *ofs_p += add_user_info("Author", fmt, dst, - line + 7, dmode, encoding); - } - - if (!memcmp(line, "committer ", 10) && - (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) { - len = linelen; - if (fmt == CMIT_FMT_EMAIL) - len = bound_rfc2047(linelen, encoding); - ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p); - dst = *buf_p + *ofs_p; - *ofs_p += add_user_info("Commit", fmt, dst, - line + 10, dmode, encoding); - } - } -} - -static void pp_title_line(enum cmit_fmt fmt, - const char **msg_p, - unsigned long *len_p, - unsigned long *ofs_p, - char **buf_p, - unsigned long *space_p, - int indent, - const char *subject, - const char *after_subject, - const char *encoding, - int plain_non_ascii) -{ - char *title; - unsigned long title_alloc, title_len; - unsigned long len; - - title_len = 0; - title_alloc = 80; - title = xmalloc(title_alloc); - for (;;) { - const char *line = *msg_p; - int linelen = get_one_line(line, *len_p); - *msg_p += linelen; - *len_p -= linelen; - - if (!linelen || is_empty_line(line, &linelen)) - break; - - if (title_alloc <= title_len + linelen + 2) { - title_alloc = title_len + linelen + 80; - title = xrealloc(title, title_alloc); - } - len = 0; - if (title_len) { - if (fmt == CMIT_FMT_EMAIL) { - len++; - title[title_len++] = '\n'; - } - len++; - title[title_len++] = ' '; - } - memcpy(title + title_len, line, linelen); - title_len += linelen; - } - - /* Enough slop for the MIME header and rfc2047 */ - len = bound_rfc2047(title_len, encoding)+ 1000; - if (subject) - len += strlen(subject); - if (after_subject) - len += strlen(after_subject); - if (encoding) - len += strlen(encoding); - ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p); - - if (subject) { - len = strlen(subject); - memcpy(*buf_p + *ofs_p, subject, len); - *ofs_p += len; - *ofs_p += add_rfc2047(*buf_p + *ofs_p, - title, title_len, encoding); - } else { - memcpy(*buf_p + *ofs_p, title, title_len); - *ofs_p += title_len; - } - (*buf_p)[(*ofs_p)++] = '\n'; - if (plain_non_ascii) { - const char *header_fmt = - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=%s\n" - "Content-Transfer-Encoding: 8bit\n"; - *ofs_p += snprintf(*buf_p + *ofs_p, - *space_p - *ofs_p, - header_fmt, encoding); - } - if (after_subject) { - len = strlen(after_subject); - memcpy(*buf_p + *ofs_p, after_subject, len); - *ofs_p += len; - } - free(title); - if (fmt == CMIT_FMT_EMAIL) { - ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p); - (*buf_p)[(*ofs_p)++] = '\n'; - } -} - -static void pp_remainder(enum cmit_fmt fmt, - const char **msg_p, - unsigned long *len_p, - unsigned long *ofs_p, - char **buf_p, - unsigned long *space_p, - int indent) -{ - int first = 1; - for (;;) { - const char *line = *msg_p; - int linelen = get_one_line(line, *len_p); - *msg_p += linelen; - *len_p -= linelen; - - if (!linelen) - break; - - if (is_empty_line(line, &linelen)) { - if (first) - continue; - if (fmt == CMIT_FMT_SHORT) - break; - } - first = 0; - - ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p); - if (indent) { - memset(*buf_p + *ofs_p, ' ', indent); - *ofs_p += indent; - } - memcpy(*buf_p + *ofs_p, line, linelen); - *ofs_p += linelen; - (*buf_p)[(*ofs_p)++] = '\n'; - } -} - -unsigned long pretty_print_commit(enum cmit_fmt fmt, - const struct commit *commit, - unsigned long len, - char **buf_p, unsigned long *space_p, - int abbrev, const char *subject, - const char *after_subject, - enum date_mode dmode, - int plain_non_ascii) -{ - unsigned long offset = 0; - unsigned long beginning_of_body; - int indent = 4; - const char *msg = commit->buffer; - char *reencoded; - const char *encoding; - char *buf; - - if (fmt == CMIT_FMT_USERFORMAT) - return format_commit_message(commit, msg, buf_p, space_p); - - encoding = (git_log_output_encoding - ? git_log_output_encoding - : git_commit_encoding); - if (!encoding) - encoding = "utf-8"; - reencoded = logmsg_reencode(commit, encoding); - if (reencoded) { - msg = reencoded; - len = strlen(reencoded); - } - - if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) - indent = 0; - - /* After-subject is used to pass in Content-Type: multipart - * MIME header; in that case we do not have to do the - * plaintext content type even if the commit message has - * non 7-bit ASCII character. Otherwise, check if we need - * to say this is not a 7-bit ASCII. - */ - if (fmt == CMIT_FMT_EMAIL && !after_subject) { - int i, ch, in_body; - - for (in_body = i = 0; (ch = msg[i]) && i < len; i++) { - if (!in_body) { - /* author could be non 7-bit ASCII but - * the log may be so; skip over the - * header part first. - */ - if (ch == '\n' && - i + 1 < len && msg[i+1] == '\n') - in_body = 1; - } - else if (non_ascii(ch)) { - plain_non_ascii = 1; - break; - } - } - } - - pp_header(fmt, abbrev, dmode, encoding, - commit, &msg, &len, - &offset, buf_p, space_p); - if (fmt != CMIT_FMT_ONELINE && !subject) { - ALLOC_GROW(*buf_p, offset + 20, *space_p); - (*buf_p)[offset++] = '\n'; - } - - /* Skip excess blank lines at the beginning of body, if any... */ - for (;;) { - int linelen = get_one_line(msg, len); - int ll = linelen; - if (!linelen) - break; - if (!is_empty_line(msg, &ll)) - break; - msg += linelen; - len -= linelen; - } - - /* These formats treat the title line specially. */ - if (fmt == CMIT_FMT_ONELINE - || fmt == CMIT_FMT_EMAIL) - pp_title_line(fmt, &msg, &len, &offset, - buf_p, space_p, indent, - subject, after_subject, encoding, - plain_non_ascii); - - beginning_of_body = offset; - if (fmt != CMIT_FMT_ONELINE) - pp_remainder(fmt, &msg, &len, &offset, - buf_p, space_p, indent); - - while (offset && isspace((*buf_p)[offset-1])) - offset--; - - ALLOC_GROW(*buf_p, offset + 20, *space_p); - buf = *buf_p; - - /* Make sure there is an EOLN for the non-oneline case */ - if (fmt != CMIT_FMT_ONELINE) - buf[offset++] = '\n'; - - /* - * The caller may append additional body text in e-mail - * format. Make sure we did not strip the blank line - * between the header and the body. - */ - if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body) - buf[offset++] = '\n'; - buf[offset] = '\0'; - free(reencoded); - return offset; -} - struct commit *pop_commit(struct commit_list **stack) { struct commit_list *top = *stack; @@ -1279,125 +415,95 @@ struct commit *pop_commit(struct commit_list **stack) return item; } -void topo_sort_default_setter(struct commit *c, void *data) -{ - c->util = data; -} - -void *topo_sort_default_getter(struct commit *c) -{ - return c->util; -} - /* * Performs an in-place topological sort on the list supplied. */ void sort_in_topological_order(struct commit_list ** list, int lifo) { - sort_in_topological_order_fn(list, lifo, topo_sort_default_setter, - topo_sort_default_getter); -} - -void sort_in_topological_order_fn(struct commit_list ** list, int lifo, - topo_sort_set_fn_t setter, - topo_sort_get_fn_t getter) -{ - struct commit_list * next = *list; - struct commit_list * work = NULL, **insert; - struct commit_list ** pptr = list; - struct sort_node * nodes; - struct sort_node * next_nodes; - int count = 0; - - /* determine the size of the list */ - while (next) { - next = next->next; - count++; - } + struct commit_list *next, *orig = *list; + struct commit_list *work, **insert; + struct commit_list **pptr; - if (!count) + if (!orig) return; - /* allocate an array to help sort the list */ - nodes = xcalloc(count, sizeof(*nodes)); - /* link the list to the array */ - next_nodes = nodes; - next=*list; - while (next) { - next_nodes->list_item = next; - setter(next->item, next_nodes); - next_nodes++; - next = next->next; + *list = NULL; + + /* Mark them and clear the indegree */ + for (next = orig; next; next = next->next) { + struct commit *commit = next->item; + commit->object.flags |= TOPOSORT; + commit->indegree = 0; } + /* update the indegree */ - next=*list; - while (next) { + for (next = orig; next; next = next->next) { struct commit_list * parents = next->item->parents; while (parents) { - struct commit * parent=parents->item; - struct sort_node * pn = (struct sort_node *) getter(parent); + struct commit *parent = parents->item; - if (pn) - pn->indegree++; - parents=parents->next; + if (parent->object.flags & TOPOSORT) + parent->indegree++; + parents = parents->next; } - next=next->next; } + /* - * find the tips - * - * tips are nodes not reachable from any other node in the list - * - * the tips serve as a starting set for the work queue. - */ - next=*list; + * find the tips + * + * tips are nodes not reachable from any other node in the list + * + * the tips serve as a starting set for the work queue. + */ + work = NULL; insert = &work; - while (next) { - struct sort_node * node = (struct sort_node *) getter(next->item); + for (next = orig; next; next = next->next) { + struct commit *commit = next->item; - if (node->indegree == 0) { - insert = &commit_list_insert(next->item, insert)->next; - } - next=next->next; + if (!commit->indegree) + insert = &commit_list_insert(commit, insert)->next; } /* process the list in topological order */ if (!lifo) sort_by_date(&work); + + pptr = list; + *list = NULL; while (work) { - struct commit * work_item = pop_commit(&work); - struct sort_node * work_node = (struct sort_node *) getter(work_item); - struct commit_list * parents = work_item->parents; + struct commit *commit; + struct commit_list *parents, *work_item; - while (parents) { - struct commit * parent=parents->item; - struct sort_node * pn = (struct sort_node *) getter(parent); - - if (pn) { - /* - * parents are only enqueued for emission - * when all their children have been emitted thereby - * guaranteeing topological order. - */ - pn->indegree--; - if (!pn->indegree) { - if (!lifo) - insert_by_date(parent, &work); - else - commit_list_insert(parent, &work); - } + work_item = work; + work = work_item->next; + work_item->next = NULL; + + commit = work_item->item; + for (parents = commit->parents; parents ; parents = parents->next) { + struct commit *parent=parents->item; + + if (!(parent->object.flags & TOPOSORT)) + continue; + + /* + * parents are only enqueued for emission + * when all their children have been emitted thereby + * guaranteeing topological order. + */ + if (!--parent->indegree) { + if (!lifo) + insert_by_date(parent, &work); + else + commit_list_insert(parent, &work); } - parents=parents->next; } /* - * work_item is a commit all of whose children - * have already been emitted. we can emit it now. - */ - *pptr = work_node->list_item; - pptr = &(*pptr)->next; - *pptr = NULL; - setter(work_item, NULL); + * work_item is a commit all of whose children + * have already been emitted. we can emit it now. + */ + commit->object.flags &= ~TOPOSORT; + *pptr = work_item; + pptr = &work_item->next; } - free(nodes); } /* merge-base stuff */ @@ -1487,8 +593,7 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) } struct commit_list *get_merge_bases(struct commit *one, - struct commit *two, - int cleanup) + struct commit *two, int cleanup) { struct commit_list *list; struct commit **rslt; @@ -3,6 +3,7 @@ #include "object.h" #include "tree.h" +#include "strbuf.h" #include "decorate.h" struct commit_list { @@ -13,6 +14,7 @@ struct commit_list { struct commit { struct object object; void *util; + unsigned int indegree; unsigned long date; struct commit_list *parents; struct tree *tree; @@ -62,7 +64,13 @@ enum cmit_fmt { extern int non_ascii(int); extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode, int non_ascii_present); +extern void format_commit_message(const struct commit *commit, + const void *format, struct strbuf *sb); +extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, + struct strbuf *, + int abbrev, const char *subject, + const char *after_subject, enum date_mode, + int non_ascii_present); /** Removes the first commit from a list sorted by date, and adds all * of its parents. @@ -77,31 +85,12 @@ void clear_commit_marks(struct commit *commit, unsigned int mark); /* * Performs an in-place topological sort of list supplied. * - * Pre-conditions for sort_in_topological_order: - * all commits in input list and all parents of those - * commits must have object.util == NULL - * - * Pre-conditions for sort_in_topological_order_fn: - * all commits in input list and all parents of those - * commits must have getter(commit) == NULL - * - * Post-conditions: * invariant of resulting list is: * a reachable from b => ord(b) < ord(a) * in addition, when lifo == 0, commits on parallel tracks are * sorted in the dates order. */ - -typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); -typedef void* (*topo_sort_get_fn_t)(struct commit*); - -void topo_sort_default_setter(struct commit *c, void *data); -void *topo_sort_default_getter(struct commit *c); - void sort_in_topological_order(struct commit_list ** list, int lifo); -void sort_in_topological_order_fn(struct commit_list ** list, int lifo, - topo_sort_set_fn_t setter, - topo_sort_get_fn_t getter); struct commit_graft { unsigned char sha1[20]; @@ -123,4 +112,14 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); int in_merge_bases(struct commit *, struct commit **, int); + +extern int interactive_add(void); +extern void add_files_to_cache(int verbose, const char *prefix, const char **files); +extern int rerere(void); + +static inline int single_parent(struct commit *commit) +{ + return commit->parents && !commit->parents->next; +} + #endif /* COMMIT_H */ diff --git a/compat/memmem.c b/compat/memmem.c new file mode 100644 index 0000000000..cd0d877364 --- /dev/null +++ b/compat/memmem.c @@ -0,0 +1,29 @@ +#include "../git-compat-util.h" + +void *gitmemmem(const void *haystack, size_t haystack_len, + const void *needle, size_t needle_len) +{ + const char *begin = haystack; + const char *last_possible = begin + haystack_len - needle_len; + + /* + * The first occurrence of the empty string is deemed to occur at + * the beginning of the string. + */ + if (needle_len == 0) + return (void *)begin; + + /* + * Sanity check, otherwise the loop might search through the whole + * memory. + */ + if (haystack_len < needle_len) + return NULL; + + for (; begin <= last_possible; begin++) { + if (!memcmp(begin, needle, needle_len)) + return (void *)begin; + } + + return NULL; +} diff --git a/compat/mkdtemp.c b/compat/mkdtemp.c new file mode 100644 index 0000000000..34d4b49818 --- /dev/null +++ b/compat/mkdtemp.c @@ -0,0 +1,8 @@ +#include "../git-compat-util.h" + +char *gitmkdtemp(char *template) +{ + if (!mktemp(template) || mkdir(template, 0700)) + return NULL; + return template; +} @@ -6,6 +6,7 @@ * */ #include "cache.h" +#include "exec_cmd.h" #define MAXNAME (256) @@ -459,6 +460,21 @@ int git_config_from_file(config_fn_t fn, const char *filename) return ret; } +const char *git_etc_gitconfig(void) +{ + static const char *system_wide; + if (!system_wide) { + system_wide = ETC_GITCONFIG; + if (!is_absolute_path(system_wide)) { + /* interpret path relative to exec-dir */ + const char *exec_path = git_exec_path(); + system_wide = prefix_path(exec_path, strlen(exec_path), + system_wide); + } + } + return system_wide; +} + int git_config(config_fn_t fn) { int ret = 0; @@ -471,8 +487,8 @@ int git_config(config_fn_t fn) * config file otherwise. */ filename = getenv(CONFIG_ENVIRONMENT); if (!filename) { - if (!access(ETC_GITCONFIG, R_OK)) - ret += git_config_from_file(fn, ETC_GITCONFIG); + if (!access(git_etc_gitconfig(), R_OK)) + ret += git_config_from_file(fn, git_etc_gitconfig()); home = getenv("HOME"); filename = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!filename) diff --git a/config.mak.in b/config.mak.in index 776b805659..11d256e9cf 100644 --- a/config.mak.in +++ b/config.mak.in @@ -35,7 +35,10 @@ NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ NO_IPV6=@NO_IPV6@ NO_C99_FORMAT=@NO_C99_FORMAT@ NO_STRCASESTR=@NO_STRCASESTR@ +NO_MEMMEM=@NO_MEMMEM@ NO_STRLCPY=@NO_STRLCPY@ +NO_STRTOUMAX=@NO_STRTOUMAX@ NO_SETENV=@NO_SETENV@ +NO_MKDTEMP=@NO_MKDTEMP@ NO_ICONV=@NO_ICONV@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ diff --git a/configure.ac b/configure.ac index ab516db379..7bcf1a488c 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ fi \ AC_ARG_WITH([lib], [AS_HELP_STRING([--with-lib=ARG], [ARG specifies alternative name for lib directory])], - [if test "$withval" = "no" -o "$withval" = "yes"; then \ + [if test "$withval" = "no" || test "$withval" = "yes"; then \ AC_MSG_WARN([You should provide name for --with-lib=ARG]); \ else \ GIT_CONF_APPEND_LINE(lib=$withval); \ @@ -265,9 +265,9 @@ AC_RUN_IFELSE( [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], [[char buf[64]; if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) - exit(1); + return 1; else if (strcmp(buf, "12345")) - exit(2);]])], + return 2;]])], [ac_cv_c_c99_format=yes], [ac_cv_c_c99_format=no]) ]) @@ -289,18 +289,36 @@ AC_CHECK_FUNC(strcasestr, [NO_STRCASESTR=YesPlease]) AC_SUBST(NO_STRCASESTR) # +# Define NO_MEMMEM if you don't have memmem. +AC_CHECK_FUNC(memmem, +[NO_MEMMEM=], +[NO_MEMMEM=YesPlease]) +AC_SUBST(NO_MEMMEM) +# # Define NO_STRLCPY if you don't have strlcpy. AC_CHECK_FUNC(strlcpy, [NO_STRLCPY=], [NO_STRLCPY=YesPlease]) AC_SUBST(NO_STRLCPY) # +# Define NO_STRTOUMAX if you don't have strtoumax in the C library. +AC_CHECK_FUNC(strtoumax, +[NO_STRTOUMAX=], +[NO_STRTOUMAX=YesPlease]) +AC_SUBST(NO_STRTOUMAX) +# # Define NO_SETENV if you don't have setenv in the C library. AC_CHECK_FUNC(setenv, [NO_SETENV=], [NO_SETENV=YesPlease]) AC_SUBST(NO_SETENV) # +# Define NO_MKDTEMP if you don't have mkdtemp in the C library. +AC_CHECK_FUNC(mkdtemp, +[NO_MKDTEMP=], +[NO_MKDTEMP=YesPlease]) +AC_SUBST(NO_MKDTEMP) +# # Define NO_MMAP if you want to avoid mmap. # # Define NO_ICONV if your libc does not properly support iconv. @@ -36,6 +36,11 @@ static int check_ref(const char *name, int len, unsigned int flags) return !(flags & ~REF_NORMAL); } +int check_ref_type(const struct ref *ref, int flags) +{ + return check_ref(ref->name, strlen(ref->name), flags); +} + /* * Read all the refs from the other end */ @@ -72,9 +77,9 @@ struct ref **get_remote_heads(int in, struct ref **list, continue; if (nr_match && !path_match(name, nr_match, match)) continue; - ref = alloc_ref(len - 40); + ref = alloc_ref(name_len + 1); hashcpy(ref->old_sha1, old_sha1); - memcpy(ref->name, buffer + 41, len - 40); + memcpy(ref->name, buffer + 41, name_len + 1); *list = ref; list = &ref->next; } @@ -393,9 +398,7 @@ static int git_proxy_command_options(const char *var, const char *value) if (matchlen == 4 && !memcmp(value, "none", 4)) matchlen = 0; - git_proxy_command = xmalloc(matchlen + 1); - memcpy(git_proxy_command, value, matchlen); - git_proxy_command[matchlen] = 0; + git_proxy_command = xmemdupz(value, matchlen); } return 0; } @@ -470,24 +473,27 @@ char *get_port(char *host) } /* - * This returns 0 if the transport protocol does not need fork(2), - * or a process id if it does. Once done, finish the connection + * This returns NULL if the transport protocol does not need fork(2), or a + * struct child_process object if it does. Once done, finish the connection * with finish_connect() with the value returned from this function - * (it is safe to call finish_connect() with 0 to support the former + * (it is safe to call finish_connect() with NULL to support the former * case). * - * Does not return a negative value on error; it just dies. + * If it returns, the connect is successful; it just dies on errors. */ -pid_t git_connect(int fd[2], char *url, const char *prog, int flags) +struct child_process *git_connect(int fd[2], const char *url_orig, + const char *prog, int flags) { + char *url = xstrdup(url_orig); char *host, *path = url; char *end; int c; - int pipefd[2][2]; - pid_t pid; + struct child_process *conn; enum protocol protocol = PROTO_LOCAL; int free_path = 0; char *port = NULL; + const char **arg; + struct strbuf cmd; /* Without this we cannot rely on waitpid() to tell * what happened to our children. @@ -568,79 +574,72 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags) prog, path, 0, target_host, 0); free(target_host); + free(url); if (free_path) free(path); - return 0; + return NULL; } - if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0) - die("unable to create pipe pair for communication"); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - char command[MAX_CMD_LEN]; - char *posn = command; - int size = MAX_CMD_LEN; - int of = 0; - - of |= add_to_string(&posn, &size, prog, 0); - of |= add_to_string(&posn, &size, " ", 0); - of |= add_to_string(&posn, &size, path, 1); - - if (of) - die("command line too long"); - - dup2(pipefd[1][0], 0); - dup2(pipefd[0][1], 1); - close(pipefd[0][0]); - close(pipefd[0][1]); - close(pipefd[1][0]); - close(pipefd[1][1]); - if (protocol == PROTO_SSH) { - const char *ssh, *ssh_basename; - ssh = getenv("GIT_SSH"); - if (!ssh) ssh = "ssh"; - ssh_basename = strrchr(ssh, '/'); - if (!ssh_basename) - ssh_basename = ssh; - else - ssh_basename++; - - if (!port) - execlp(ssh, ssh_basename, host, command, NULL); - else - execlp(ssh, ssh_basename, "-p", port, host, - command, NULL); + conn = xcalloc(1, sizeof(*conn)); + + strbuf_init(&cmd, MAX_CMD_LEN); + strbuf_addstr(&cmd, prog); + strbuf_addch(&cmd, ' '); + sq_quote_buf(&cmd, path); + if (cmd.len >= MAX_CMD_LEN) + die("command line too long"); + + conn->in = conn->out = -1; + conn->argv = arg = xcalloc(6, sizeof(*arg)); + if (protocol == PROTO_SSH) { + const char *ssh = getenv("GIT_SSH"); + if (!ssh) ssh = "ssh"; + + *arg++ = ssh; + if (port) { + *arg++ = "-p"; + *arg++ = port; } - else { - unsetenv(ALTERNATE_DB_ENVIRONMENT); - unsetenv(DB_ENVIRONMENT); - unsetenv(GIT_DIR_ENVIRONMENT); - unsetenv(GIT_WORK_TREE_ENVIRONMENT); - unsetenv(GRAFT_ENVIRONMENT); - unsetenv(INDEX_ENVIRONMENT); - execlp("sh", "sh", "-c", command, NULL); - } - die("exec failed"); + *arg++ = host; } - fd[0] = pipefd[0][0]; - fd[1] = pipefd[1][1]; - close(pipefd[0][1]); - close(pipefd[1][0]); + else { + /* remove these from the environment */ + const char *env[] = { + ALTERNATE_DB_ENVIRONMENT, + DB_ENVIRONMENT, + GIT_DIR_ENVIRONMENT, + GIT_WORK_TREE_ENVIRONMENT, + GRAFT_ENVIRONMENT, + INDEX_ENVIRONMENT, + NULL + }; + conn->env = env; + *arg++ = "sh"; + *arg++ = "-c"; + } + *arg++ = cmd.buf; + *arg = NULL; + + if (start_command(conn)) + die("unable to fork"); + + fd[0] = conn->out; /* read from child's stdout */ + fd[1] = conn->in; /* write to child's stdin */ + strbuf_release(&cmd); + free(url); if (free_path) free(path); - return pid; + return conn; } -int finish_connect(pid_t pid) +int finish_connect(struct child_process *conn) { - if (pid == 0) + int code; + if (!conn) return 0; - while (waitpid(pid, NULL, 0) < 0) { - if (errno != EINTR) - return -1; - } - return 0; + code = finish_command(conn); + free(conn->argv); + free(conn); + return code; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index cad842af45..58e0e53cd6 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -299,7 +299,6 @@ __git_commands () check-attr) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; - convert-objects) : plumbing;; cvsexportcommit) : export;; cvsimport) : import;; cvsserver) : daemon;; @@ -347,7 +346,6 @@ __git_commands () ssh-*) : transport;; stripspace) : plumbing;; svn) : import export;; - svnimport) : import;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; @@ -553,6 +551,20 @@ _git_describe () _git_diff () { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--cached --stat --numstat --shortstat --summary + --patch-with-stat --name-only --name-status --color + --no-color --color-words --no-renames --check + --full-index --binary --abbrev --diff-filter + --find-copies-harder --pickaxe-all --pickaxe-regex + --text --ignore-space-at-eol --ignore-space-change + --ignore-all-space --exit-code --quiet --ext-diff + --no-ext-diff" + return + ;; + esac __git_complete_file } diff --git a/convert-objects.c b/contrib/convert-objects/convert-objects.c index 90e7900e6d..90e7900e6d 100644 --- a/convert-objects.c +++ b/contrib/convert-objects/convert-objects.c diff --git a/Documentation/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt index 9718abf86d..9718abf86d 100644 --- a/Documentation/git-convert-objects.txt +++ b/contrib/convert-objects/git-convert-objects.txt diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 280557ecd4..e147da0596 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -36,7 +36,6 @@ ;; TODO ;; - portability to XEmacs ;; - better handling of subprocess errors -;; - hook into file save (after-save-hook) ;; - diff against other branch ;; - renaming files from the status buffer ;; - creating tags @@ -97,6 +96,21 @@ if there is already one that displays the same directory." :group 'git :type 'string) +(defcustom git-show-uptodate nil + "Whether to display up-to-date files." + :group 'git + :type 'boolean) + +(defcustom git-show-ignored nil + "Whether to display ignored files." + :group 'git + :type 'boolean) + +(defcustom git-show-unknown t + "Whether to display unknown files." + :group 'git + :type 'boolean) + (defface git-status-face '((((class color) (background light)) (:foreground "purple")) @@ -205,22 +219,15 @@ and returns the process output as a string." (message "Running git %s...done" (car args)) buffer)) -(defun git-run-command (buffer env &rest args) - (message "Running git %s..." (car args)) - (apply #'git-call-process-env buffer env args) - (message "Running git %s...done" (car args))) - (defun git-run-command-region (buffer start end env &rest args) "Run a git command with specified buffer region as input." - (message "Running git %s..." (car args)) (unless (eq 0 (if env (git-run-process-region buffer start end "env" (append (git-get-env-strings env) (list "git") args)) (git-run-process-region buffer start end "git" args))) - (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string))) - (message "Running git %s...done" (car args))) + (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))) (defun git-run-hook (hook env &rest args) "Run a git hook and display its output if any." @@ -297,6 +304,13 @@ and returns the process output as a string." "\"") name)) +(defun git-success-message (text files) + "Print a success message after having handled FILES." + (let ((n (length files))) + (if (equal n 1) + (message "%s %s" text (car files)) + (message "%s %d files" text n)))) + (defun git-get-top-dir (dir) "Retrieve the top-level directory of a git tree." (let ((cdup (with-output-to-string @@ -323,7 +337,7 @@ and returns the process output as a string." (sort-lines nil (point-min) (point-max)) (save-buffer)) (when created - (git-run-command nil nil "update-index" "--add" "--" (file-relative-name ignore-name))) + (git-call-process-env nil nil "update-index" "--add" "--" (file-relative-name ignore-name))) (git-update-status-files (list (file-relative-name ignore-name)) 'unknown))) ; propertize definition for XEmacs, stolen from erc-compat @@ -470,14 +484,36 @@ and returns the process output as a string." "Remove everything from the status list." (ewoc-filter status (lambda (info) nil))) -(defun git-set-files-state (files state) - "Set the state of a list of files." - (dolist (info files) - (unless (eq (git-fileinfo->state info) state) - (setf (git-fileinfo->state info) state) - (setf (git-fileinfo->rename-state info) nil) - (setf (git-fileinfo->orig-name info) nil) - (setf (git-fileinfo->needs-refresh info) t)))) +(defun git-set-fileinfo-state (info state) + "Set the state of a file info." + (unless (eq (git-fileinfo->state info) state) + (setf (git-fileinfo->state info) state + (git-fileinfo->old-perm info) 0 + (git-fileinfo->new-perm info) 0 + (git-fileinfo->rename-state info) nil + (git-fileinfo->orig-name info) nil + (git-fileinfo->needs-refresh info) t))) + +(defun git-status-filenames-map (status func files &rest args) + "Apply FUNC to the status files names in the FILES list." + (when files + (setq files (sort files #'string-lessp)) + (let ((file (pop files)) + (node (ewoc-nth status 0))) + (while (and file node) + (let ((info (ewoc-data node))) + (if (string-lessp (git-fileinfo->name info) file) + (setq node (ewoc-next status node)) + (if (string-equal (git-fileinfo->name info) file) + (apply func info args)) + (setq file (pop files)))))))) + +(defun git-set-filenames-state (status files state) + "Set the state of a list of named files." + (when files + (git-status-filenames-map status #'git-set-fileinfo-state files state) + (unless state ;; delete files whose state has been set to nil + (ewoc-filter status (lambda (info) (git-fileinfo->state info)))))) (defun git-state-code (code) "Convert from a string to a added/deleted/modified state." @@ -532,21 +568,38 @@ and returns the process output as a string." " " (git-escape-file-name (git-fileinfo->name info)) (git-rename-as-string info)))) -(defun git-insert-fileinfo (status info &optional refresh) - "Insert INFO in the status buffer, optionally refreshing an existing one." - (let ((node (and refresh - (git-find-status-file status (git-fileinfo->name info))))) - (setf (git-fileinfo->needs-refresh info) t) - (when node ;preserve the marked flag - (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node)))) - (if node (setf (ewoc-data node) info) (ewoc-enter-last status info)))) +(defun git-insert-info-list (status infolist) + "Insert a list of file infos in the status buffer, replacing existing ones if any." + (setq infolist (sort infolist + (lambda (info1 info2) + (string-lessp (git-fileinfo->name info1) + (git-fileinfo->name info2))))) + (let ((info (pop infolist)) + (node (ewoc-nth status 0))) + (while info + (setf (git-fileinfo->needs-refresh info) t) + (cond ((not node) + (ewoc-enter-last status info) + (setq info (pop infolist))) + ((string-lessp (git-fileinfo->name (ewoc-data node)) + (git-fileinfo->name info)) + (setq node (ewoc-next status node))) + ((string-equal (git-fileinfo->name (ewoc-data node)) + (git-fileinfo->name info)) + ;; preserve the marked flag + (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))) + (setf (ewoc-data node) info) + (setq info (pop infolist))) + (t + (ewoc-enter-before status node info) + (setq info (pop infolist))))))) (defun git-run-diff-index (status files) "Run git-diff-index on FILES and parse the results into STATUS. Return the list of files that haven't been handled." - (let ((refresh files)) + (let (infolist) (with-temp-buffer - (apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files) + (apply #'git-call-process-env t nil "diff-index" "-z" "-M" "HEAD" "--" files) (goto-char (point-min)) (while (re-search-forward ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0" @@ -558,13 +611,14 @@ Return the list of files that haven't been handled." (new-name (match-string 8))) (if new-name ; copy or rename (if (eq ?C (string-to-char state)) - (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh) - (git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh) - (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh) - (git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh)) + (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist) + (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist) + (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist)) + (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist)) (setq files (delete name files)) - (when new-name (setq files (delete new-name files))))))) - files) + (when new-name (setq files (delete new-name files)))))) + (git-insert-info-list status infolist) + files)) (defun git-find-status-file (status file) "Find a given file in the status ewoc and return its node." @@ -576,27 +630,26 @@ Return the list of files that haven't been handled." (defun git-run-ls-files (status files default-state &rest options) "Run git-ls-files on FILES and parse the results into STATUS. Return the list of files that haven't been handled." - (let ((refresh files)) + (let (infolist) (with-temp-buffer - (apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files)) + (apply #'git-call-process-env t nil "ls-files" "-z" (append options (list "--") files)) (goto-char (point-min)) - (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1) - (let ((state (match-string 1)) - (name (match-string 2))) - (git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh) - (setq files (delete name files)))))) - files) + (while (re-search-forward "\\([^\0]*\\)\0" nil t 1) + (let ((name (match-string 1))) + (push (git-create-fileinfo default-state name) infolist) + (setq files (delete name files))))) + (git-insert-info-list status infolist) + files)) (defun git-run-ls-unmerged (status files) "Run git-ls-files -u on FILES and parse the results into STATUS." (with-temp-buffer - (apply #'git-run-command t nil "ls-files" "-z" "-u" "--" files) + (apply #'git-call-process-env t nil "ls-files" "-z" "-u" "--" files) (goto-char (point-min)) (let (unmerged-files) (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t) - (let ((node (git-find-status-file status (match-string 1)))) - (when node (push (ewoc-data node) unmerged-files)))) - (git-set-files-state unmerged-files 'unmerged)))) + (push (match-string 1) unmerged-files)) + (git-set-filenames-state status unmerged-files 'unmerged)))) (defun git-get-exclude-files () "Get the list of exclude files to pass to git-ls-files." @@ -608,34 +661,30 @@ Return the list of files that haven't been handled." (push config files)) files)) +(defun git-run-ls-files-with-excludes (status files default-state &rest options) + "Run git-ls-files on FILES with appropriate --exclude-from options." + (let ((exclude-files (git-get-exclude-files))) + (apply #'git-run-ls-files status files default-state + (concat "--exclude-per-directory=" git-per-dir-ignore-file) + (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) + (defun git-update-status-files (files &optional default-state) "Update the status of FILES from the index." (unless git-status (error "Not in git-status buffer.")) - (let* ((status git-status) - (remaining-files + (unless files + (when git-show-uptodate (git-run-ls-files git-status nil 'uptodate "-c"))) + (let* ((remaining-files (if (git-empty-db-p) ; we need some special handling for an empty db - (git-run-ls-files status files 'added "-c") - (git-run-diff-index status files)))) - (git-run-ls-unmerged status files) - (when (or (not files) remaining-files) - (let ((exclude-files (git-get-exclude-files))) - (setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o" - (concat "--exclude-per-directory=" git-per-dir-ignore-file) - (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) - ; mark remaining files with the default state (or remove them if nil) - (when remaining-files - (if default-state - (ewoc-map (lambda (info) - (when (member (git-fileinfo->name info) remaining-files) - (git-set-files-state (list info) default-state)) - nil) - status) - (ewoc-filter status - (lambda (info files) - (not (member (git-fileinfo->name info) files))) - remaining-files))) + (git-run-ls-files git-status files 'added "-c") + (git-run-diff-index git-status files)))) + (git-run-ls-unmerged git-status files) + (when (or remaining-files (and git-show-unknown (not files))) + (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o"))) + (when (or remaining-files (and git-show-ignored (not files))) + (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i"))) + (git-set-filenames-state git-status remaining-files default-state) (git-refresh-files) - (git-refresh-ewoc-hf status))) + (git-refresh-ewoc-hf git-status))) (defun git-marked-files () "Return a list of all marked files, or if none a list containing just the file at cursor position." @@ -698,11 +747,11 @@ Return the list of files that haven't been handled." ('deleted (push info deleted)) ('modified (push info modified)))) (when added - (apply #'git-run-command nil env "update-index" "--add" "--" (git-get-filenames added))) + (apply #'git-call-process-env nil env "update-index" "--add" "--" (git-get-filenames added))) (when deleted - (apply #'git-run-command nil env "update-index" "--remove" "--" (git-get-filenames deleted))) + (apply #'git-call-process-env nil env "update-index" "--remove" "--" (git-get-filenames deleted))) (when modified - (apply #'git-run-command nil env "update-index" "--" (git-get-filenames modified))))) + (apply #'git-call-process-env nil env "update-index" "--" (git-get-filenames modified))))) (defun git-run-pre-commit-hook () "Run the pre-commit hook if any." @@ -734,6 +783,7 @@ Return the list of files that haven't been handled." head-tree (git-rev-parse "HEAD^{tree}"))) (if files (progn + (message "Running git commit...") (git-read-tree head-tree index-file) (git-update-index nil files) ;update both the default index (git-update-index index-file files) ;and the temporary one @@ -744,8 +794,9 @@ Return the list of files that haven't been handled." (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) (with-current-buffer buffer (erase-buffer)) - (git-set-files-state files 'uptodate) - (git-run-command nil nil "rerere") + (dolist (info files) (git-set-fileinfo-state info 'uptodate)) + (git-call-process-env nil nil "rerere") + (git-call-process-env nil nil "gc" "--auto") (git-refresh-files) (git-refresh-ewoc-hf git-status) (message "Committed %s." commit) @@ -792,7 +843,8 @@ Return the list of files that haven't been handled." "Mark all files." (interactive) (unless git-status (error "Not in git-status buffer.")) - (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status) + (ewoc-map (lambda (info) (unless (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) t))) git-status) ; move back to goal column after invalidate (when goal-column (move-to-column goal-column))) @@ -800,7 +852,9 @@ Return the list of files that haven't been handled." "Unmark all files." (interactive) (unless git-status (error "Not in git-status buffer.")) - (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status) + (ewoc-map (lambda (info) (when (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) nil) + t)) git-status) ; move back to goal column after invalidate (when goal-column (move-to-column goal-column))) @@ -853,11 +907,12 @@ Return the list of files that haven't been handled." (defun git-add-file () "Add marked file(s) to the index cache." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unknown)))) + (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) - (apply #'git-run-command nil nil "update-index" "--add" "--" files) - (git-update-status-files files 'uptodate))) + (apply #'git-call-process-env nil nil "update-index" "--add" "--" files) + (git-update-status-files files 'uptodate) + (git-success-message "Added" files))) (defun git-ignore-file () "Add marked file(s) to the ignore list." @@ -866,12 +921,13 @@ Return the list of files that haven't been handled." (unless files (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files)) (dolist (f files) (git-append-to-ignore f)) - (git-update-status-files files 'ignored))) + (git-update-status-files files 'ignored) + (git-success-message "Ignored" files))) (defun git-remove-file () "Remove the marked file(s)." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate)))) + (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored)))) (unless files (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files)) (if (yes-or-no-p @@ -879,8 +935,9 @@ Return the list of files that haven't been handled." (progn (dolist (name files) (when (file-exists-p name) (delete-file name))) - (apply #'git-run-command nil nil "update-index" "--remove" "--" files) - (git-update-status-files files nil)) + (apply #'git-call-process-env nil nil "update-index" "--remove" "--" files) + (git-update-status-files files nil) + (git-success-message "Removed" files)) (message "Aborting")))) (defun git-revert-file () @@ -898,29 +955,65 @@ Return the list of files that haven't been handled." ('unmerged (push (git-fileinfo->name info) modified)) ('modified (push (git-fileinfo->name info) modified)))) (when added - (apply #'git-run-command nil nil "update-index" "--force-remove" "--" added)) + (apply #'git-call-process-env nil nil "update-index" "--force-remove" "--" added)) (when modified - (apply #'git-run-command nil nil "checkout" "HEAD" modified)) - (git-update-status-files (append added modified) 'uptodate)))) + (apply #'git-call-process-env nil nil "checkout" "HEAD" modified)) + (git-update-status-files (append added modified) 'uptodate) + (git-success-message "Reverted" (git-get-filenames files))))) (defun git-resolve-file () "Resolve conflicts in marked file(s)." (interactive) (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) (when files - (apply #'git-run-command nil nil "update-index" "--" files) - (git-update-status-files files 'uptodate)))) + (apply #'git-call-process-env nil nil "update-index" "--" files) + (git-update-status-files files 'uptodate) + (git-success-message "Resolved" files)))) (defun git-remove-handled () "Remove handled files from the status list." (interactive) (ewoc-filter git-status (lambda (info) - (not (or (eq (git-fileinfo->state info) 'ignored) - (eq (git-fileinfo->state info) 'uptodate))))) + (case (git-fileinfo->state info) + ('ignored git-show-ignored) + ('uptodate git-show-uptodate) + ('unknown git-show-unknown) + (t t)))) (unless (ewoc-nth git-status 0) ; refresh header if list is empty (git-refresh-ewoc-hf git-status))) +(defun git-toggle-show-uptodate () + "Toogle the option for showing up-to-date files." + (interactive) + (if (setq git-show-uptodate (not git-show-uptodate)) + (git-refresh-status) + (git-remove-handled))) + +(defun git-toggle-show-ignored () + "Toogle the option for showing ignored files." + (interactive) + (if (setq git-show-ignored (not git-show-ignored)) + (progn + (message "Inserting ignored files...") + (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i") + (git-refresh-files) + (git-refresh-ewoc-hf git-status) + (message "Inserting ignored files...done")) + (git-remove-handled))) + +(defun git-toggle-show-unknown () + "Toogle the option for showing unknown files." + (interactive) + (if (setq git-show-unknown (not git-show-unknown)) + (progn + (message "Inserting unknown files...") + (git-run-ls-files-with-excludes git-status nil 'unknown "-o") + (git-refresh-files) + (git-refresh-ewoc-hf git-status) + (message "Inserting unknown files...done")) + (git-remove-handled))) + (defun git-setup-diff-buffer (buffer) "Setup a buffer for displaying a diff." (let ((dir default-directory)) @@ -1118,12 +1211,23 @@ Return the list of files that haven't been handled." (interactive) (let* ((status git-status) (pos (ewoc-locate status)) + (marked-files (git-get-filenames (ewoc-collect status (lambda (info) (git-fileinfo->marked info))))) (cur-name (and pos (git-fileinfo->name (ewoc-data pos))))) (unless status (error "Not in git-status buffer.")) - (git-run-command nil nil "update-index" "--refresh") + (message "Refreshing git status...") + (git-call-process-env nil nil "update-index" "--refresh") (git-clear-status status) (git-update-status-files nil) + ; restore file marks + (when marked-files + (git-status-filenames-map status + (lambda (info) + (setf (git-fileinfo->marked info) t) + (setf (git-fileinfo->needs-refresh info) t)) + marked-files) + (git-refresh-files)) ; move point to the current file name if any + (message "Refreshing git status...done") (let ((node (and cur-name (git-find-status-file status cur-name)))) (when node (ewoc-goto-node status node))))) @@ -1146,7 +1250,8 @@ Return the list of files that haven't been handled." (unless git-status-mode-map (let ((map (make-keymap)) - (diff-map (make-sparse-keymap))) + (diff-map (make-sparse-keymap)) + (toggle-map (make-sparse-keymap))) (suppress-keymap map) (define-key map "?" 'git-help) (define-key map "h" 'git-help) @@ -1170,6 +1275,7 @@ Return the list of files that haven't been handled." (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) (define-key map "R" 'git-resolve-file) + (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) (define-key map "U" 'git-revert-file) @@ -1186,6 +1292,11 @@ Return the list of files that haven't been handled." (define-key diff-map "h" 'git-diff-file-merge-head) (define-key diff-map "m" 'git-diff-file-mine) (define-key diff-map "o" 'git-diff-file-other) + ; the toggle submap + (define-key toggle-map "u" 'git-toggle-show-uptodate) + (define-key toggle-map "i" 'git-toggle-show-ignored) + (define-key toggle-map "k" 'git-toggle-show-unknown) + (define-key toggle-map "m" 'git-toggle-all-marks) (setq git-status-mode-map map))) ;; git mode should only run in the *git status* buffer @@ -1207,6 +1318,9 @@ Commands: (let ((status (ewoc-create 'git-fileinfo-prettyprint "" ""))) (set (make-local-variable 'git-status) status)) (set (make-local-variable 'list-buffers-directory) default-directory) + (make-local-variable 'git-show-uptodate) + (make-local-variable 'git-show-ignored) + (make-local-variable 'git-show-unknown) (run-hooks 'git-status-mode-hook))) (defun git-find-status-buffer (dir) @@ -1235,9 +1349,24 @@ Commands: (cd dir) (git-status-mode) (git-refresh-status) - (goto-char (point-min))) + (goto-char (point-min)) + (add-hook 'after-save-hook 'git-update-saved-file)) (message "%s is not a git working tree." dir))) +(defun git-update-saved-file () + "Update the corresponding git-status buffer when a file is saved. +Meant to be used in `after-save-hook'." + (let* ((file (expand-file-name buffer-file-name)) + (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil))) + (buffer (and dir (git-find-status-buffer dir)))) + (when buffer + (with-current-buffer buffer + (let ((filename (file-relative-name file dir))) + ; skip files located inside the .git directory + (unless (string-match "^\\.git/" filename) + (git-call-process-env nil nil "add" "--refresh" "--" filename) + (git-update-status-files (list filename) 'uptodate))))))) + (defun git-help () "Display help for Git mode." (interactive) diff --git a/git-clean.sh b/contrib/examples/git-clean.sh index 931d1aa4e4..01c95e9fe8 100755 --- a/git-clean.sh +++ b/contrib/examples/git-clean.sh @@ -3,16 +3,22 @@ # Copyright (c) 2005-2006 Pavel Roskin # -USAGE="[-d] [-f] [-n] [-q] [-x | -X] [--] <paths>..." -LONG_USAGE='Clean untracked files from the working directory - -d remove directories as well - -f override clean.requireForce and clean anyway - -n don'\''t remove anything, just show what would be done - -q be quiet, only report errors - -x remove ignored files as well - -X remove only ignored files +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-clean [options] <paths>... + +Clean untracked files from the working directory + When optional <paths>... arguments are given, the paths -affected are further limited to those that match them.' +affected are further limited to those that match them. +-- +d remove directories as well +f override clean.requireForce and clean anyway +n don't remove anything, just show what would be done +q be quiet, only report errors +x remove ignored files as well +X remove only ignored files" + SUBDIRECTORY_OK=Yes . git-sh-setup require_work_tree @@ -20,12 +26,13 @@ require_work_tree ignored= ignoredonly= cleandir= -disabled="`git config --bool clean.requireForce`" rmf="rm -f --" rmrf="rm -rf --" rm_refuse="echo Not removing" echo1="echo" +disabled=$(git config --bool clean.requireForce) + while test $# != 0 do case "$1" in @@ -33,10 +40,10 @@ do cleandir=1 ;; -f) - disabled= + disabled=false ;; -n) - disabled= + disabled=false rmf="echo Would remove" rmrf="echo Would remove" rm_refuse="echo Would not remove" @@ -55,24 +62,29 @@ do shift break ;; - -*) - usage - ;; *) - break + usage # should not happen + ;; esac shift done -if [ "$disabled" = true ]; then - echo "clean.requireForce set and -n or -f not given; refusing to clean" - exit 1 -fi - -case "$ignored,$ignoredonly" in - 1,1) usage;; +# requireForce used to default to false but now it defaults to true. +# IOW, lack of explicit "clean.requireForce = false" is taken as +# "clean.requireForce = true". +case "$disabled" in +"") + die "clean.requireForce not set and -n or -f not given; refusing to clean" + ;; +"true") + die "clean.requireForce set and -n or -f not given; refusing to clean" + ;; esac +if [ "$ignored,$ignoredonly" = "1,1" ]; then + die "-x and -X cannot be set together" +fi + if [ -z "$ignored" ]; then excl="--exclude-per-directory=.gitignore" excl_info= excludes_file= diff --git a/git-fetch.sh b/contrib/examples/git-fetch.sh index e44af2c86d..e44af2c86d 100755 --- a/git-fetch.sh +++ b/contrib/examples/git-fetch.sh diff --git a/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh index fec70bbf88..fec70bbf88 100755 --- a/git-ls-remote.sh +++ b/contrib/examples/git-ls-remote.sh diff --git a/git-merge-ours.sh b/contrib/examples/git-merge-ours.sh index 29dba4ba3a..29dba4ba3a 100755 --- a/git-merge-ours.sh +++ b/contrib/examples/git-merge-ours.sh diff --git a/git-reset.sh b/contrib/examples/git-reset.sh index bafeb52cd1..bafeb52cd1 100755 --- a/git-reset.sh +++ b/contrib/examples/git-reset.sh diff --git a/git-svnimport.perl b/contrib/examples/git-svnimport.perl index aa5b3b2c97..ea8c1b2f60 100755 --- a/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl @@ -54,6 +54,7 @@ my $branch_name = $opt_b || "branches"; my $project_name = $opt_P || ""; $project_name = "/" . $project_name if ($project_name); my $repack_after = $opt_R || 1000; +my $root_pool = SVN::Pool->new_default; @ARGV == 1 or @ARGV == 2 or usage(); @@ -132,7 +133,7 @@ sub conn { my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider, SVN::Client::get_ssl_server_trust_file_provider, SVN::Client::get_username_provider]); - my $s = SVN::Ra->new(url => $repo, auth => $auth); + my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool); die "SVN connection to $repo: $!\n" unless defined $s; $self->{'svn'} = $s; $self->{'repo'} = $repo; @@ -147,11 +148,10 @@ sub file { print "... $rev $path ...\n" if $opt_v; my (undef, $properties); - my $pool = SVN::Pool->new(); $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; eval { (undef, $properties) - = $self->{'svn'}->get_file($path,$rev,$fh,$pool); }; - $pool->clear; + = $self->{'svn'}->get_file($path,$rev,$fh); }; if($@) { return undef if $@ =~ /Attempted to get checksum/; die $@; @@ -185,6 +185,7 @@ sub ignore { print "... $rev $path ...\n" if $opt_v; $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; my (undef,undef,$properties) = $self->{'svn'}->get_dir($path,$rev,undef); if (exists $properties->{'svn:ignore'}) { @@ -202,6 +203,7 @@ sub ignore { sub dir_list { my($self,$path,$rev) = @_; $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; my ($dirents,undef,$properties) = $self->{'svn'}->get_dir($path,$rev,undef); return $dirents; @@ -358,10 +360,9 @@ open BRANCHES,">>", "$git_dir/svn2git"; sub node_kind($$) { my ($svnpath, $revision) = @_; - my $pool=SVN::Pool->new; $svnpath =~ s#^/*##; - my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool); - $pool->clear; + my $subpool = SVN::Pool::new_default_sub; + my $kind = $svn->{'svn'}->check_path($svnpath,$revision); return $kind; } @@ -889,7 +890,7 @@ sub commit_all { # Recursive use of the SVN connection does not work local $svn = $svn2; - my ($changed_paths, $revision, $author, $date, $message, $pool) = @_; + my ($changed_paths, $revision, $author, $date, $message) = @_; my %p; while(my($path,$action) = each %$changed_paths) { $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ]; @@ -925,14 +926,14 @@ print "Processing from $current_rev to $opt_l ...\n" if $opt_v; my $from_rev; my $to_rev = $current_rev - 1; +my $subpool = SVN::Pool::new_default_sub; while ($to_rev < $opt_l) { + $subpool->clear; $from_rev = $to_rev + 1; $to_rev = $from_rev + $repack_after; $to_rev = $opt_l if $opt_l < $to_rev; print "Fetching from $from_rev to $to_rev ...\n" if $opt_v; - my $pool=SVN::Pool->new; - $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all,$pool); - $pool->clear; + $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all); my $pid = fork(); die "Fork: $!\n" unless defined $pid; unless($pid) { diff --git a/Documentation/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 71aad8b45b..71aad8b45b 100644 --- a/Documentation/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt diff --git a/contrib/fast-import/git-import.perl b/contrib/fast-import/git-import.perl new file mode 100755 index 0000000000..f9fef6db28 --- /dev/null +++ b/contrib/fast-import/git-import.perl @@ -0,0 +1,64 @@ +#!/usr/bin/perl +# +# Performs an initial import of a directory. This is the equivalent +# of doing 'git init; git add .; git commit'. It's a little slower, +# but is meant to be a simple fast-import example. + +use strict; +use File::Find; + +my $USAGE = 'Usage: git-import branch import-message'; +my $branch = shift or die "$USAGE\n"; +my $message = shift or die "$USAGE\n"; + +chomp(my $username = `git config user.name`); +chomp(my $email = `git config user.email`); +die 'You need to set user name and email' + unless $username && $email; + +system('git init'); +open(my $fi, '|-', qw(git fast-import --date-format=now)) + or die "unable to spawn fast-import: $!"; + +print $fi <<EOF; +commit refs/heads/$branch +committer $username <$email> now +data <<MSGEOF +$message +MSGEOF + +EOF + +find( + sub { + if($File::Find::name eq './.git') { + $File::Find::prune = 1; + return; + } + return unless -f $_; + + my $fn = $File::Find::name; + $fn =~ s#^.\/##; + + open(my $in, '<', $_) + or die "unable to open $fn: $!"; + my @st = stat($in) + or die "unable to stat $fn: $!"; + my $len = $st[7]; + + print $fi "M 644 inline $fn\n"; + print $fi "data $len\n"; + while($len > 0) { + my $r = read($in, my $buf, $len < 4096 ? $len : 4096); + defined($r) or die "read error from $fn: $!"; + $r > 0 or die "premature EOF from $fn: $!"; + print $fi $buf; + $len -= $r; + } + print $fi "\n"; + + }, '.' +); + +close($fi); +exit $?; diff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh new file mode 100755 index 0000000000..0ca7718d05 --- /dev/null +++ b/contrib/fast-import/git-import.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Performs an initial import of a directory. This is the equivalent +# of doing 'git init; git add .; git commit'. It's a lot slower, +# but is meant to be a simple fast-import example. + +if [ -z "$1" -o -z "$2" ]; then + echo "Usage: git-import branch import-message" + exit 1 +fi + +USERNAME="$(git config user.name)" +EMAIL="$(git config user.email)" + +if [ -z "$USERNAME" -o -z "$EMAIL" ]; then + echo "You need to set user name and email" + exit 1 +fi + +git init + +( + cat <<EOF +commit refs/heads/$1 +committer $USERNAME <$EMAIL> now +data <<MSGEOF +$2 +MSGEOF + +EOF + find * -type f|while read i;do + echo "M 100644 inline $i" + echo data $(stat -c '%s' "$i") + cat "$i" + echo + done + echo +) | git fast-import --date-format=now diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 65c57ac4d8..c80a6da252 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -71,6 +71,79 @@ def isP4Exec(kind): a plus sign, it is also executable""" return (re.search(r"(^[cku]?x)|\+.*x", kind) != None) +def setP4ExecBit(file, mode): + # Reopens an already open file and changes the execute bit to match + # the execute bit setting in the passed in mode. + + p4Type = "+x" + + if not isModeExec(mode): + p4Type = getP4OpenedType(file) + p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type) + p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type) + if p4Type[-1] == "+": + p4Type = p4Type[0:-1] + + system("p4 reopen -t %s %s" % (p4Type, file)) + +def getP4OpenedType(file): + # Returns the perforce file type for the given file. + + result = read_pipe("p4 opened %s" % file) + match = re.match(".*\((.+)\)$", result) + if match: + return match.group(1) + else: + die("Could not determine file type for %s" % file) + +def diffTreePattern(): + # This is a simple generator for the diff tree regex pattern. This could be + # a class variable if this and parseDiffTreeEntry were a part of a class. + pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)') + while True: + yield pattern + +def parseDiffTreeEntry(entry): + """Parses a single diff tree entry into its component elements. + + See git-diff-tree(1) manpage for details about the format of the diff + output. This method returns a dictionary with the following elements: + + src_mode - The mode of the source file + dst_mode - The mode of the destination file + src_sha1 - The sha1 for the source file + dst_sha1 - The sha1 fr the destination file + status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc) + status_score - The score for the status (applicable for 'C' and 'R' + statuses). This is None if there is no score. + src - The path for the source file. + dst - The path for the destination file. This is only present for + copy or renames. If it is not present, this is None. + + If the pattern is not matched, None is returned.""" + + match = diffTreePattern().next().match(entry) + if match: + return { + 'src_mode': match.group(1), + 'dst_mode': match.group(2), + 'src_sha1': match.group(3), + 'dst_sha1': match.group(4), + 'status': match.group(5), + 'status_score': match.group(6), + 'src': match.group(7), + 'dst': match.group(10) + } + return None + +def isModeExec(mode): + # Returns True if the given git mode represents an executable file, + # otherwise False. + return mode[-3:] == "755" + +def isModeExecChanged(src_mode, dst_mode): + return isModeExec(src_mode) != isModeExec(dst_mode) + def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): cmd = "p4 -G %s" % cmd if verbose: @@ -289,6 +362,19 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent def originP4BranchesExist(): return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master") +def p4ChangesForPaths(depotPaths, changeRange): + assert depotPaths + output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange) + for p in depotPaths])) + + changes = [] + for line in output: + changeNum = line.split(" ")[1] + changes.append(int(changeNum)) + + changes.sort() + return changes + class Command: def __init__(self): self.usage = "usage: %prog [options]" @@ -386,6 +472,7 @@ class P4Submit(Command): optparse.make_option("--dry-run", action="store_true"), optparse.make_option("--direct", dest="directSubmit", action="store_true"), optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"), + optparse.make_option("-M", dest="detectRename", action="store_true"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -398,6 +485,7 @@ class P4Submit(Command): self.origin = "" self.directSubmit = False self.trustMeLikeAFool = False + self.detectRename = False self.verbose = False self.isWindows = (platform.system() == "Windows") @@ -478,24 +566,39 @@ class P4Submit(Command): diff = self.diffStatus else: print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id)) - diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)) + diffOpts = ("", "-M")[self.detectRename] + diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id)) filesToAdd = set() filesToDelete = set() editedFiles = set() + filesToChangeExecBit = {} for line in diff: - modifier = line[0] - path = line[1:].strip() + diff = parseDiffTreeEntry(line) + modifier = diff['status'] + path = diff['src'] if modifier == "M": system("p4 edit \"%s\"" % path) + if isModeExecChanged(diff['src_mode'], diff['dst_mode']): + filesToChangeExecBit[path] = diff['dst_mode'] editedFiles.add(path) elif modifier == "A": filesToAdd.add(path) + filesToChangeExecBit[path] = diff['dst_mode'] if path in filesToDelete: filesToDelete.remove(path) elif modifier == "D": filesToDelete.add(path) if path in filesToAdd: filesToAdd.remove(path) + elif modifier == "R": + src, dest = diff['src'], diff['dst'] + system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest)) + system("p4 edit \"%s\"" % (dest)) + if isModeExecChanged(diff['src_mode'], diff['dst_mode']): + filesToChangeExecBit[dest] = diff['dst_mode'] + os.unlink(dest) + editedFiles.add(dest) + filesToDelete.add(src) else: die("unknown modifier %s for %s" % (modifier, path)) @@ -516,6 +619,10 @@ class P4Submit(Command): "and with .rej files / [w]rite the patch to a file (patch.txt) ") if response == "s": print "Skipping! Good luck with the next patches..." + for f in editedFiles: + system("p4 revert \"%s\"" % f); + for f in filesToAdd: + system("rm %s" %f) return elif response == "a": os.system(applyPatchCmd) @@ -541,6 +648,11 @@ class P4Submit(Command): system("p4 revert \"%s\"" % f) system("p4 delete \"%s\"" % f) + # Set/clear executable bits + for f in filesToChangeExecBit.keys(): + mode = filesToChangeExecBit[f] + setP4ExecBit(f, mode) + logMessage = "" if not self.directSubmit: logMessage = extractLogMessageFromGitCommit(id) @@ -672,9 +784,8 @@ class P4Submit(Command): f.close(); os.chdir(self.clientPath) - response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath) - if response == "y" or response == "yes": - system("p4 sync ...") + print "Syncronizing p4 checkout..." + system("p4 sync ...") if self.reset: self.firstTime = True @@ -713,10 +824,14 @@ class P4Submit(Command): else: print "All changes applied!" os.chdir(self.oldWorkingDirectory) - response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ") + + sync = P4Sync() + sync.run([]) + + response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ") if response == "y" or response == "yes": rebase = P4Rebase() - rebase.run([]) + rebase.rebase() os.remove(self.configFile) return True @@ -1026,7 +1141,7 @@ class P4Sync(Command): l = p4CmdList("labels %s..." % ' '.join (self.depotPaths)) if len(l) > 0 and not self.silent: - print "Finding files belonging to labels in %s" % `self.depotPath` + print "Finding files belonging to labels in %s" % `self.depotPaths` for output in l: label = output["label"] @@ -1092,6 +1207,15 @@ class P4Sync(Command): for branch in lostAndFoundBranches: self.knownBranches[branch] = branch + def getBranchMappingFromGitBranches(self): + branches = p4BranchesInGit(self.importIntoRemotes) + for branch in branches.keys(): + if branch == "master": + branch = "main" + else: + branch = branch[len(self.projectName):] + self.knownBranches[branch] = branch + def listExistingP4GitBranches(self): # branches holds mapping from name to commit branches = p4BranchesInGit(self.importIntoRemotes) @@ -1110,6 +1234,186 @@ class P4Sync(Command): self.keepRepoPath = (d.has_key('options') and ('keepRepoPath' in d['options'])) + def gitRefForBranch(self, branch): + if branch == "main": + return self.refPrefix + "master" + + if len(branch) <= 0: + return branch + + return self.refPrefix + self.projectName + branch + + def gitCommitByP4Change(self, ref, change): + if self.verbose: + print "looking in ref " + ref + " for change %s using bisect..." % change + + earliestCommit = "" + latestCommit = parseRevision(ref) + + while True: + if self.verbose: + print "trying: earliest %s latest %s" % (earliestCommit, latestCommit) + next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip() + if len(next) == 0: + if self.verbose: + print "argh" + return "" + log = extractLogMessageFromGitCommit(next) + settings = extractSettingsGitLog(log) + currentChange = int(settings['change']) + if self.verbose: + print "current change %s" % currentChange + + if currentChange == change: + if self.verbose: + print "found %s" % next + return next + + if currentChange < change: + earliestCommit = "^%s" % next + else: + latestCommit = "%s" % next + + return "" + + def importNewBranch(self, branch, maxChange): + # make fast-import flush all changes to disk and update the refs using the checkpoint + # command so that we can try to find the branch parent in the git history + self.gitStream.write("checkpoint\n\n"); + self.gitStream.flush(); + branchPrefix = self.depotPaths[0] + branch + "/" + range = "@1,%s" % maxChange + #print "prefix" + branchPrefix + changes = p4ChangesForPaths([branchPrefix], range) + if len(changes) <= 0: + return False + firstChange = changes[0] + #print "first change in branch: %s" % firstChange + sourceBranch = self.knownBranches[branch] + sourceDepotPath = self.depotPaths[0] + sourceBranch + sourceRef = self.gitRefForBranch(sourceBranch) + #print "source " + sourceBranch + + branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"]) + #print "branch parent: %s" % branchParentChange + gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange) + if len(gitParent) > 0: + self.initialParents[self.gitRefForBranch(branch)] = gitParent + #print "parent git commit: %s" % gitParent + + self.importChanges(changes) + return True + + def importChanges(self, changes): + cnt = 1 + for change in changes: + description = p4Cmd("describe %s" % change) + self.updateOptionDict(description) + + if not self.silent: + sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes))) + sys.stdout.flush() + cnt = cnt + 1 + + try: + if self.detectBranches: + branches = self.splitFilesIntoBranches(description) + for branch in branches.keys(): + ## HACK --hwn + branchPrefix = self.depotPaths[0] + branch + "/" + + parent = "" + + filesForCommit = branches[branch] + + if self.verbose: + print "branch is %s" % branch + + self.updatedBranches.add(branch) + + if branch not in self.createdBranches: + self.createdBranches.add(branch) + parent = self.knownBranches[branch] + if parent == branch: + parent = "" + else: + fullBranch = self.projectName + branch + if fullBranch not in self.p4BranchesInGit: + if not self.silent: + print("\n Importing new branch %s" % fullBranch); + if self.importNewBranch(branch, change - 1): + parent = "" + self.p4BranchesInGit.append(fullBranch) + if not self.silent: + print("\n Resuming with change %s" % change); + + if self.verbose: + print "parent determined through known branches: %s" % parent + + branch = self.gitRefForBranch(branch) + parent = self.gitRefForBranch(parent) + + if self.verbose: + print "looking for initial parent for %s; current parent is %s" % (branch, parent) + + if len(parent) == 0 and branch in self.initialParents: + parent = self.initialParents[branch] + del self.initialParents[branch] + + self.commit(description, filesForCommit, branch, [branchPrefix], parent) + else: + files = self.extractFilesFromCommit(description) + self.commit(description, files, self.branch, self.depotPaths, + self.initialParent) + self.initialParent = "" + except IOError: + print self.gitError.read() + sys.exit(1) + + def importHeadRevision(self, revision): + print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch) + + details = { "user" : "git perforce import user", "time" : int(time.time()) } + details["desc"] = ("Initial import of %s from the state at revision %s" + % (' '.join(self.depotPaths), revision)) + details["change"] = revision + newestRevision = 0 + + fileCnt = 0 + for info in p4CmdList("files " + + ' '.join(["%s...%s" + % (p, revision) + for p in self.depotPaths])): + + if info['code'] == 'error': + sys.stderr.write("p4 returned an error: %s\n" + % info['data']) + sys.exit(1) + + + change = int(info["change"]) + if change > newestRevision: + newestRevision = change + + if info["action"] == "delete": + # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! + #fileCnt = fileCnt + 1 + continue + + for prop in ["depotFile", "rev", "action", "type" ]: + details["%s%s" % (prop, fileCnt)] = info[prop] + + fileCnt = fileCnt + 1 + + details["change"] = newestRevision + self.updateOptionDict(details) + try: + self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths) + except IOError: + print "IO error with git fast-import. Is your git version recent enough?" + print self.gitError.read() + + def run(self, args): self.depotPaths = [] self.changeRange = "" @@ -1207,7 +1511,7 @@ class P4Sync(Command): self.depotPaths = sorted(args) - self.revision = "" + revision = "" self.users = {} newPaths = [] @@ -1218,15 +1522,15 @@ class P4Sync(Command): if self.changeRange == "@all": self.changeRange = "" elif ',' not in self.changeRange: - self.revision = self.changeRange + revision = self.changeRange self.changeRange = "" p = p[:atIdx] elif p.find("#") != -1: hashIdx = p.index("#") - self.revision = p[hashIdx:] + revision = p[hashIdx:] p = p[:hashIdx] elif self.previousDepotPaths == []: - self.revision = "#head" + revision = "#head" p = re.sub ("\.\.\.$", "", p) if not p.endswith("/"): @@ -1246,8 +1550,10 @@ class P4Sync(Command): ## FIXME - what's a P4 projectName ? self.projectName = self.guessProjectName() - if not self.hasOrigin: - self.getBranchMapping(); + if self.hasOrigin: + self.getBranchMappingFromGitBranches() + else: + self.getBranchMapping() if self.verbose: print "p4-git branches: %s" % self.p4BranchesInGit print "initial parents: %s" % self.initialParents @@ -1267,49 +1573,8 @@ class P4Sync(Command): self.gitStream = importProcess.stdin self.gitError = importProcess.stderr - if self.revision: - print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch) - - details = { "user" : "git perforce import user", "time" : int(time.time()) } - details["desc"] = ("Initial import of %s from the state at revision %s" - % (' '.join(self.depotPaths), self.revision)) - details["change"] = self.revision - newestRevision = 0 - - fileCnt = 0 - for info in p4CmdList("files " - + ' '.join(["%s...%s" - % (p, self.revision) - for p in self.depotPaths])): - - if info['code'] == 'error': - sys.stderr.write("p4 returned an error: %s\n" - % info['data']) - sys.exit(1) - - - change = int(info["change"]) - if change > newestRevision: - newestRevision = change - - if info["action"] == "delete": - # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! - #fileCnt = fileCnt + 1 - continue - - for prop in ["depotFile", "rev", "action", "type" ]: - details["%s%s" % (prop, fileCnt)] = info[prop] - - fileCnt = fileCnt + 1 - - details["change"] = newestRevision - self.updateOptionDict(details) - try: - self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths) - except IOError: - print "IO error with git fast-import. Is your git version recent enough?" - print self.gitError.read() - + if revision: + self.importHeadRevision(revision) else: changes = [] @@ -1327,15 +1592,7 @@ class P4Sync(Command): if self.verbose: print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), self.changeRange) - assert self.depotPaths - output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange) - for p in self.depotPaths])) - - for line in output: - changeNum = line.split(" ")[1] - changes.append(int(changeNum)) - - changes.sort() + changes = p4ChangesForPaths(self.depotPaths, self.changeRange) if len(self.maxChanges) > 0: changes = changes[:min(int(self.maxChanges), len(changes))] @@ -1350,74 +1607,7 @@ class P4Sync(Command): self.updatedBranches = set() - cnt = 1 - for change in changes: - description = p4Cmd("describe %s" % change) - self.updateOptionDict(description) - - if not self.silent: - sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes))) - sys.stdout.flush() - cnt = cnt + 1 - - try: - if self.detectBranches: - branches = self.splitFilesIntoBranches(description) - for branch in branches.keys(): - ## HACK --hwn - branchPrefix = self.depotPaths[0] + branch + "/" - - parent = "" - - filesForCommit = branches[branch] - - if self.verbose: - print "branch is %s" % branch - - self.updatedBranches.add(branch) - - if branch not in self.createdBranches: - self.createdBranches.add(branch) - parent = self.knownBranches[branch] - if parent == branch: - parent = "" - elif self.verbose: - print "parent determined through known branches: %s" % parent - - # main branch? use master - if branch == "main": - branch = "master" - else: - - ## FIXME - branch = self.projectName + branch - - if parent == "main": - parent = "master" - elif len(parent) > 0: - ## FIXME - parent = self.projectName + parent - - branch = self.refPrefix + branch - if len(parent) > 0: - parent = self.refPrefix + parent - - if self.verbose: - print "looking for initial parent for %s; current parent is %s" % (branch, parent) - - if len(parent) == 0 and branch in self.initialParents: - parent = self.initialParents[branch] - del self.initialParents[branch] - - self.commit(description, filesForCommit, branch, [branchPrefix], parent) - else: - files = self.extractFilesFromCommit(description) - self.commit(description, files, self.branch, self.depotPaths, - self.initialParent) - self.initialParent = "" - except IOError: - print self.gitError.read() - sys.exit(1) + self.importChanges(changes) if not self.silent: print "" @@ -1427,7 +1617,6 @@ class P4Sync(Command): sys.stdout.write("%s " % b) sys.stdout.write("\n") - self.gitStream.close() if importProcess.wait() != 0: die("fast-import failed: %s" % self.gitError.read()) @@ -1448,6 +1637,9 @@ class P4Rebase(Command): sync = P4Sync() sync.run([]) + return self.rebase() + + def rebase(self): [upstream, settings] = findUpstreamBranchPoint() if len(upstream) == 0: die("Cannot find upstream branchpoint for rebase") @@ -1569,6 +1761,7 @@ def printUsage(commands): commands = { "debug" : P4Debug, "submit" : P4Submit, + "commit" : P4Submit, "sync" : P4Sync, "rebase" : P4Rebase, "clone" : P4Clone, diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index 5931766620..4c99dfb903 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -27,12 +27,17 @@ import math import string import fcntl +have_gtksourceview2 = False +have_gtksourceview = False try: - import gtksourceview - have_gtksourceview = True + import gtksourceview2 + have_gtksourceview2 = True except ImportError: - have_gtksourceview = False - print "Running without gtksourceview module" + try: + import gtksourceview + have_gtksourceview = True + except ImportError: + print "Running without gtksourceview2 or gtksourceview module" re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})') @@ -58,6 +63,26 @@ def show_date(epoch, tz): return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs)) +def get_source_buffer_and_view(): + if have_gtksourceview2: + buffer = gtksourceview2.Buffer() + slm = gtksourceview2.LanguageManager() + gsl = slm.get_language("diff") + buffer.set_highlight_syntax(True) + buffer.set_language(gsl) + view = gtksourceview2.View(buffer) + elif have_gtksourceview: + buffer = gtksourceview.SourceBuffer() + slm = gtksourceview.SourceLanguagesManager() + gsl = slm.get_language_from_mime_type("text/x-patch") + buffer.set_highlight(True) + buffer.set_language(gsl) + view = gtksourceview.SourceView(buffer) + else: + buffer = gtk.TextBuffer() + view = gtk.TextView(buffer) + return (buffer, view) + class CellRendererGraph(gtk.GenericCellRenderer): """Cell renderer for directed graph. @@ -582,17 +607,7 @@ class DiffWindow(object): hpan.pack1(scrollwin, True, True) scrollwin.show() - if have_gtksourceview: - self.buffer = gtksourceview.SourceBuffer() - slm = gtksourceview.SourceLanguagesManager() - gsl = slm.get_language_from_mime_type("text/x-patch") - self.buffer.set_highlight(True) - self.buffer.set_language(gsl) - sourceview = gtksourceview.SourceView(self.buffer) - else: - self.buffer = gtk.TextBuffer() - sourceview = gtk.TextView(self.buffer) - + (self.buffer, sourceview) = get_source_buffer_and_view() sourceview.set_editable(False) sourceview.modify_font(pango.FontDescription("Monospace")) @@ -956,16 +971,7 @@ class GitView(object): vbox.pack_start(scrollwin, expand=True, fill=True) scrollwin.show() - if have_gtksourceview: - self.message_buffer = gtksourceview.SourceBuffer() - slm = gtksourceview.SourceLanguagesManager() - gsl = slm.get_language_from_mime_type("text/x-patch") - self.message_buffer.set_highlight(True) - self.message_buffer.set_language(gsl) - sourceview = gtksourceview.SourceView(self.message_buffer) - else: - self.message_buffer = gtk.TextBuffer() - sourceview = gtk.TextView(self.message_buffer) + (self.message_buffer, sourceview) = get_source_buffer_and_view() sourceview.set_editable(False) sourceview.modify_font(pango.FontDescription("Monospace")) diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py index 37337ff01f..7a1c3e497f 100755 --- a/contrib/hg-to-git/hg-to-git.py +++ b/contrib/hg-to-git/hg-to-git.py @@ -29,6 +29,8 @@ hgvers = {} hgchildren = {} # Current branch for each hg revision hgbranch = {} +# Number of new changesets converted from hg +hgnewcsets = 0 #------------------------------------------------------------------------------ @@ -40,6 +42,8 @@ def usage(): options: -s, --gitstate=FILE: name of the state to be saved/read for incrementals + -n, --nrepack=INT: number of changesets that will trigger + a repack (default=0, -1 to deactivate) required: hgprj: name of the HG project to import (directory) @@ -68,14 +72,16 @@ def getgitenv(user, date): #------------------------------------------------------------------------------ state = '' +opt_nrepack = 0 try: - opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir=']) + opts, args = getopt.getopt(sys.argv[1:], 's:t:n:', ['gitstate=', 'tempdir=', 'nrepack=']) for o, a in opts: if o in ('-s', '--gitstate'): state = a state = os.path.abspath(state) - + if o in ('-n', '--nrepack'): + opt_nrepack = int(a) if len(args) != 1: raise('params') except: @@ -138,6 +144,7 @@ for cset in range(int(tip) + 1): # incremental, already seen if hgvers.has_key(str(cset)): continue + hgnewcsets += 1 # get info prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() @@ -222,7 +229,8 @@ for cset in range(int(tip) + 1): print 'record', cset, '->', vvv hgvers[str(cset)] = vvv -os.system('git-repack -a -d') +if hgnewcsets >= opt_nrepack and opt_nrepack != -1: + os.system('git-repack -a -d') # write the state for incrementals if state: diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 28a06c7f38..7511ea0797 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -2,24 +2,26 @@ # # Copyright (c) 2007 Andy Parkins # -# An example hook script to mail out commit update information. This hook sends emails -# listing new revisions to the repository introduced by the change being reported. The -# rule is that (for branch updates) each commit will appear on one email and one email -# only. +# An example hook script to mail out commit update information. This hook +# sends emails listing new revisions to the repository introduced by the +# change being reported. The rule is that (for branch updates) each commit +# will appear on one email and one email only. # -# This hook is stored in the contrib/hooks directory. Your distribution will have put -# this somewhere standard. You should make this script executable then link to it in -# the repository you would like to use it in. For example, on debian the hook is stored -# in /usr/share/doc/git-core/contrib/hooks/post-receive-email: +# This hook is stored in the contrib/hooks directory. Your distribution +# will have put this somewhere standard. You should make this script +# executable then link to it in the repository you would like to use it in. +# For example, on debian the hook is stored in +# /usr/share/doc/git-core/contrib/hooks/post-receive-email: # # chmod a+x post-receive-email # cd /path/to/your/repository.git # ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive # -# This hook script assumes it is enabled on the central repository of a project, with -# all users pushing only to it and not between each other. It will still work if you -# don't operate in that style, but it would become possible for the email to be from -# someone other than the person doing the push. +# This hook script assumes it is enabled on the central repository of a +# project, with all users pushing only to it and not between each other. It +# will still work if you don't operate in that style, but it would become +# possible for the email to be from someone other than the person doing the +# push. # # Config # ------ @@ -28,15 +30,17 @@ # emails for every ref update. # hooks.announcelist # This is the list that all pushes of annotated tags will go to. Leave it -# blank to default to the mailinglist field. The announce emails lists the -# short log summary of the changes since the last annotated tag. -# hook.envelopesender -# If set then the -f option is passed to sendmail to allow the envelope sender -# address to be set +# blank to default to the mailinglist field. The announce emails lists +# the short log summary of the changes since the last annotated tag. +# hooks.envelopesender +# If set then the -f option is passed to sendmail to allow the envelope +# sender address to be set +# hooks.emailprefix +# All emails have their subjects prefixed with this prefix, or "[SCM]" +# if emailprefix is unset, to aid filtering # # Notes # ----- -# All emails have their subjects prefixed with "[SCM]" to aid filtering. # All emails include the headers "X-Git-Refname", "X-Git-Oldrev", # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and # give information for debugging. @@ -49,8 +53,8 @@ # this is and calls the appropriate body-generation routine after outputting # the common header # -# Note this function doesn't actually generate any email output, that is taken -# care of by the functions it calls: +# Note this function doesn't actually generate any email output, that is +# taken care of by the functions it calls: # - generate_email_header # - generate_create_XXXX_email # - generate_update_XXXX_email @@ -138,16 +142,20 @@ generate_email() # Check if we've got anyone to send to if [ -z "$recipients" ]; then - echo >&2 "*** hooks.recipients is not set so no email will be sent" + case "$refname_type" in + "annotated tag") + config_name="hooks.announcelist" + ;; + *) + config_name="hooks.mailinglist" + ;; + esac + echo >&2 "*** $config_name is not set so no email will be sent" echo >&2 "*** for $refname update $oldrev->$newrev" exit 0 fi # Email parameters - # The committer will be obtained from the latest existing rev; so - # for a deletion it will be the oldrev, for the others, then newrev - committer=$(git show --pretty=full -s $rev | sed -ne "s/^Commit: //p" | - sed -ne 's/\(.*\) </"\1" </p') # The email subject will contain the best description of the ref # that we can build from the parameters describe=$(git describe $rev 2>/dev/null) @@ -178,7 +186,7 @@ generate_email_header() # Generate header cat <<-EOF To: $recipients - Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe + Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe X-Git-Refname: $refname X-Git-Reftype: $refname_type X-Git-Oldrev: $oldrev @@ -217,8 +225,9 @@ generate_create_branch_email() 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) + # 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 echo $LOGEND @@ -246,9 +255,10 @@ generate_update_branch_email() # # git-rev-list N ^O ^X ^N # - # So, we need to build up the list more carefully. git-rev-parse will - # generate a list of revs that may be fed into git-rev-list. We can get - # it to make the "--not --all" part and then filter out the "^N" with: + # So, we need to build up the list more carefully. git-rev-parse + # will generate a list of revs that may be fed into git-rev-list. + # We can get it to make the "--not --all" part and then filter out + # the "^N" with: # # git-rev-parse --not --all | grep -v N # @@ -258,16 +268,17 @@ generate_update_branch_email() # git-rev-list N ^O ^X # # This leaves a problem when someone else updates the repository - # while this script is running. Their new value of the ref we're working - # on would be included in the "--not --all" output; and as our $newrev - # would be an ancestor of that commit, it would exclude all of our - # commits. What we really want is to exclude the current value of - # $refname from the --not list, rather than N itself. So: + # while this script is running. Their new value of the ref we're + # working on would be included in the "--not --all" output; and as + # our $newrev would be an ancestor of that commit, it would exclude + # all of our commits. What we really want is to exclude the current + # value of $refname from the --not list, rather than N itself. So: # # git-rev-parse --not --all | grep -v $(git-rev-parse $refname) # - # Get's us to something pretty safe (apart from the small time between - # refname being read, and git-rev-parse running - for that, I give up) + # Get's us to something pretty safe (apart from the small time + # between refname being read, and git-rev-parse running - for that, + # I give up) # # # Next problem, consider this: @@ -275,18 +286,18 @@ generate_update_branch_email() # \ # * --- X --- * --- N ($newrev) # - # That is to say, there is no guarantee that oldrev is a strict subset of - # newrev (it would have required a --force, but that's allowed). So, we - # can't simply say rev-list $oldrev..$newrev. Instead we find the common - # base of the two revs and list from there. + # That is to say, there is no guarantee that oldrev is a strict + # subset of newrev (it would have required a --force, but that's + # allowed). So, we can't simply say rev-list $oldrev..$newrev. + # Instead we find the common base of the two revs and list from + # there. # - # As above, we need to take into account the presence of X; if another - # branch is already in the repository and points at some of the revisions - # that we are about to output - we don't want them. The solution is as - # before: git-rev-parse output filtered. + # As above, we need to take into account the presence of X; if + # another branch is already in the repository and points at some of + # the revisions that we are about to output - we don't want them. + # The solution is as before: git-rev-parse output filtered. # - # Finally, tags: - # 1 --- 2 --- O --- T --- 3 --- 4 --- N + # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N # # Tags pushed into the repository generate nice shortlog emails that # summarise the commits between them and the previous tag. However, @@ -294,13 +305,14 @@ generate_update_branch_email() # for a branch update. Therefore we still want to output revisions # that have been output on a tag email. # - # Luckily, git-rev-parse includes just the tool. Instead of using "--all" - # we use "--branches"; this has the added benefit that "remotes/" will - # be ignored as well. - - # List all of the revisions that were removed by this update, in a fast forward - # update, this list will be empty, because rev-list O ^N is empty. For a non - # fast forward, O ^N is the list of removed revisions + # Luckily, git-rev-parse includes just the tool. Instead of using + # "--all" we use "--branches"; this has the added benefit that + # "remotes/" will be ignored as well. + + # List all of the revisions that were removed by this update, in a + # fast forward update, this list will be empty, because rev-list O + # ^N is empty. For a non fast forward, O ^N is the list of removed + # revisions fast_forward="" rev="" for rev in $(git rev-list $newrev..$oldrev) @@ -313,10 +325,10 @@ generate_update_branch_email() fi # List all the revisions from baserev to newrev in a kind of - # "table-of-contents"; note this list can include revisions that have - # already had notification emails and is present to show the full detail - # of the change from rolling back the old revision to the base revision and - # then forward to the new revision + # "table-of-contents"; note this list can include revisions that + # have already had notification emails and is present to show the + # full detail of the change from rolling back the old revision to + # the base revision and then forward to the new revision for rev in $(git rev-list $oldrev..$newrev) do revtype=$(git cat-file -t "$rev") @@ -326,19 +338,20 @@ generate_update_branch_email() if [ "$fast_forward" ]; then echo " from $oldrev ($oldrev_type)" else - # 1. Existing revisions were removed. In this case newrev is a - # subset of oldrev - this is the reverse of a fast-forward, - # a rewind - # 2. New revisions were added on top of an old revision, this is - # a rewind and addition. + # 1. Existing revisions were removed. In this case newrev + # is a subset of oldrev - this is the reverse of a + # fast-forward, a rewind + # 2. New revisions were added on top of an old revision, + # this is a rewind and addition. - # (1) certainly happened, (2) possibly. When (2) hasn't happened, - # we set a flag to indicate that no log printout is required. + # (1) certainly happened, (2) possibly. When (2) hasn't + # happened, we set a flag to indicate that no log printout + # is required. echo "" - # Find the common ancestor of the old and new revisions and compare - # it with newrev + # Find the common ancestor of the old and new revisions and + # compare it with newrev baserev=$(git merge-base $oldrev $newrev) rewind_only="" if [ "$baserev" = "$newrev" ]; then @@ -379,21 +392,22 @@ generate_update_branch_email() git rev-parse --not --branches | grep -v $(git rev-parse $refname) | git rev-list --pretty --stdin $oldrev..$newrev - # XXX: Need a way of detecting whether git rev-list actually outputted - # anything, so that we can issue a "no new revisions added by this - # update" message + # XXX: Need a way of detecting whether git rev-list actually + # outputted anything, so that we can issue a "no new + # revisions added by this update" message echo $LOGEND else echo "No new revisions were added by this update." fi - # The diffstat is shown from the old revision to the new revision. This - # is to show the truth of what happened in this change. There's no point - # showing the stat from the base to the new revision because the base - # is effectively a random revision at this point - the user will be - # interested in what this revision changed - including the undoing of - # previous revisions in the case of non-fast forward updates. + # The diffstat is shown from the old revision to the new revision. + # This is to show the truth of what happened in this change. + # There's no point showing the stat from the base to the new + # revision because the base is effectively a random revision at this + # point - the user will be interested in what this revision changed + # - including the undoing of previous revisions in the case of + # non-fast forward updates. echo "" echo "Summary of changes:" git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev @@ -440,7 +454,8 @@ generate_update_atag_email() # generate_atag_email() { - # Use git-for-each-ref to pull out the individual fields from the tag + # Use git-for-each-ref to pull out the individual fields from the + # tag eval $(git for-each-ref --shell --format=' tagobject=%(*objectname) tagtype=%(*objecttype) @@ -451,8 +466,10 @@ generate_atag_email() echo " tagging $tagobject ($tagtype)" case "$tagtype" in commit) + # If the tagged object is a commit, then we assume this is a - # release, and so we calculate which tag this tag is replacing + # release, and so we calculate which tag this tag is + # replacing prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null) if [ -n "$prevtag" ]; then @@ -469,25 +486,27 @@ generate_atag_email() echo "" echo $LOGBEGIN - # Show the content of the tag message; this might contain a change log - # or release notes so is worth displaying. + # Show the content of the tag message; this might contain a change + # log or release notes so is worth displaying. git cat-file tag $newrev | sed -e '1,/^$/d' echo "" case "$tagtype" in commit) - # Only commit tags make sense to have rev-list operations performed - # on them + # Only commit tags make sense to have rev-list operations + # performed on them if [ -n "$prevtag" ]; then # Show changes since the previous release git rev-list --pretty=short "$prevtag..$newrev" | git shortlog else - # No previous tag, show all the changes since time began + # No previous tag, show all the changes since time + # began git rev-list --pretty=short $newrev | git shortlog fi ;; *) - # XXX: Is there anything useful we can do for non-commit objects? + # XXX: Is there anything useful we can do for non-commit + # objects? ;; esac @@ -536,13 +555,14 @@ generate_update_general_email() # generate_general_email() { - # Unannotated tags are more about marking a point than releasing a version; - # therefore we don't do the shortlog summary that we do for annotated tags - # above - we simply show that the point has been marked, and print the log - # message for the marked point for reference purposes + # Unannotated tags are more about marking a point than releasing a + # version; therefore we don't do the shortlog summary that we do for + # annotated tags above - we simply show that the point has been + # marked, and print the log message for the marked point for + # reference purposes # - # Note this section also catches any other reference type (although there - # aren't any) and deals with them in the same way. + # Note this section also catches any other reference type (although + # there aren't any) and deals with them in the same way. echo "" if [ "$newrev_type" = "commit" ]; then @@ -550,10 +570,10 @@ generate_general_email() git show --no-color --root -s $newrev echo $LOGEND else - # What can we do here? The tag marks an object that is not a commit, - # so there is no log for us to display. It's probably not wise to - # output git-cat-file as it could be a binary blob. We'll just say how - # big it is + # What can we do here? The tag marks an object that is not + # a commit, so there is no log for us to display. It's + # probably not wise to output git-cat-file as it could be a + # binary blob. We'll just say how big it is echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long." fi } @@ -582,7 +602,6 @@ send_mail() # ---------------------------- main() # --- Constants -EMAILPREFIX="[SCM] " LOGBEGIN="- Log -----------------------------------------------------------------" LOGEND="-----------------------------------------------------------------------" @@ -596,8 +615,8 @@ if [ -z "$GIT_DIR" ]; then fi projectdesc=$(sed -ne '1p' "$GIT_DIR/description") -# Check if the description is unchanged from it's default, and shorten it to a -# more manageable length if it is +# Check if the description is unchanged from it's default, and shorten it to +# a more manageable length if it is if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null then projectdesc="UNNAMED PROJECT" @@ -606,13 +625,15 @@ fi recipients=$(git repo-config hooks.mailinglist) announcerecipients=$(git repo-config hooks.announcelist) envelopesender=$(git-repo-config hooks.envelopesender) +emailprefix=$(git-repo-config hooks.emailprefix || echo '[SCM] ') # --- Main loop -# Allow dual mode: run from the command line just like the update hook, or if -# no arguments are given then run as a hook script +# Allow dual mode: run from the command line just like the update hook, or +# if no arguments are given then run as a hook script if [ -n "$1" -a -n "$2" -a -n "$3" ]; then # Output to the terminal in command line mode - if someone wanted to - # resend an email; they could redirect the output to sendmail themselves + # resend an email; they could redirect the output to sendmail + # themselves PAGER= generate_email $2 $3 $1 else while read oldrev newrev refname diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl new file mode 100644 index 0000000000..dab7c8e3a1 --- /dev/null +++ b/contrib/hooks/setgitperms.perl @@ -0,0 +1,214 @@ +#!/usr/bin/perl +# +# Copyright (c) 2006 Josh England +# +# This script can be used to save/restore full permissions and ownership data +# within a git working tree. +# +# To save permissions/ownership data, place this script in your .git/hooks +# directory and enable a `pre-commit` hook with the following lines: +# #!/bin/sh +# SUBDIRECTORY_OK=1 . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -r +# +# To restore permissions/ownership data, place this script in your .git/hooks +# directory and enable a `post-merge` and `post-checkout` hook with the +# following lines: +# #!/bin/sh +# SUBDIRECTORY_OK=1 . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -w +# +use strict; +use Getopt::Long; +use File::Find; +use File::Basename; + +my $usage = +"Usage: setgitperms.perl [OPTION]... <--read|--write> +This program uses a file `.gitmeta` to store/restore permissions and uid/gid +info for all files/dirs tracked by git in the repository. + +---------------------------------Read Mode------------------------------------- +-r, --read Reads perms/etc from working dir into a .gitmeta file +-s, --stdout Output to stdout instead of .gitmeta +-d, --diff Show unified diff of perms file (XOR with --stdout) + +---------------------------------Write Mode------------------------------------ +-w, --write Modify perms/etc in working dir to match the .gitmeta file +-v, --verbose Be verbose + +\n"; + +my ($stdout, $showdiff, $verbose, $read_mode, $write_mode); + +if ((@ARGV < 0) || !GetOptions( + "stdout", \$stdout, + "diff", \$showdiff, + "read", \$read_mode, + "write", \$write_mode, + "verbose", \$verbose, + )) { die $usage; } +die $usage unless ($read_mode xor $write_mode); + +my $topdir = `git-rev-parse --show-cdup` or die "\n"; chomp $topdir; +my $gitdir = $topdir . '.git'; +my $gitmeta = $topdir . '.gitmeta'; + +if ($write_mode) { + # Update the working dir permissions/ownership based on data from .gitmeta + open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n"; + while (defined ($_ = <IN>)) { + chomp; + if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) { + # Compare recorded perms to actual perms in the working dir + my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4); + my $fullpath = $topdir . $path; + my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath); + $wmode = sprintf "%04o", $wmode & 07777; + if ($mode ne $wmode) { + $verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n"; + chmod oct($mode), $fullpath; + } + if ($uid != $wuid || $gid != $wgid) { + if ($verbose) { + # Print out user/group names instead of uid/gid + my $pwname = getpwuid($uid); + my $grpname = getgrgid($gid); + my $wpwname = getpwuid($wuid); + my $wgrpname = getgrgid($wgid); + $pwname = $uid if !defined $pwname; + $grpname = $gid if !defined $grpname; + $wpwname = $wuid if !defined $wpwname; + $wgrpname = $wgid if !defined $wgrpname; + + print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n"; + } + chown $uid, $gid, $fullpath; + } + } + else { + warn "Invalid input format in $gitmeta:\n\t$_\n"; + } + } + close IN; +} +elsif ($read_mode) { + # Handle merge conflicts in the .gitperms file + if (-e "$gitdir/MERGE_MSG") { + if (`grep ====== $gitmeta`) { + # Conflict not resolved -- abort the commit + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Resolve the conflict in the $gitmeta file and then run\n"; + print " `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + elsif (`grep $gitmeta $gitdir/MERGE_MSG`) { + # A conflict in .gitmeta has been manually resolved. Verify that + # the working dir perms matches the current .gitmeta perms for + # each file/dir that conflicted. + # This is here because a `setgitperms.perl --write` was not + # performed due to a merge conflict, so permissions/ownership + # may not be consistent with the manually merged .gitmeta file. + my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`; + my @conflict_files; + my $metadiff = 0; + + # Build a list of files that conflicted from the .gitmeta diff + foreach my $line (@conflict_diff) { + if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) { + $metadiff = 1; + } + elsif ($line =~ /^diff --git/) { + $metadiff = 0; + } + elsif ($metadiff && $line =~ /^\+(.*) mode=/) { + push @conflict_files, $1; + } + } + + # Verify that each conflict file now has permissions consistent + # with the .gitmeta file + foreach my $file (@conflict_files) { + my $absfile = $topdir . $file; + my $gm_entry = `grep "^$file mode=" $gitmeta`; + if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) { + my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3); + my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile"); + $mode = sprintf("%04o", $mode & 07777); + if (($gm_mode ne $mode) || ($gm_uid != $uid) + || ($gm_gid != $gid)) { + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Mismatch found for file: $file\n"; + print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + } + else { + print "Warning! Permissions/ownership no longer being tracked for file: $file\n"; + } + } + } + } + + # No merge conflicts -- write out perms/ownership data to .gitmeta file + unless ($stdout) { + open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n"; + } + + my @files = `git-ls-files`; + my %dirs; + + foreach my $path (@files) { + chomp $path; + # We have to manually add stats for parent directories + my $parent = dirname($path); + while (!exists $dirs{$parent}) { + $dirs{$parent} = 1; + next if $parent eq '.'; + printstats($parent); + $parent = dirname($parent); + } + # Now the git-tracked file + printstats($path); + } + + # diff the temporary metadata file to see if anything has changed + # If no metadata has changed, don't overwrite the real file + # This is just so `git commit -a` doesn't try to commit a bogus update + unless ($stdout) { + if (! -e $gitmeta) { + rename "$gitmeta.tmp", $gitmeta; + } + else { + my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`; + if ($diff ne '') { + rename "$gitmeta.tmp", $gitmeta; + } + else { + unlink "$gitmeta.tmp"; + } + if ($showdiff) { + print $diff; + } + } + close OUT; + } + # Make sure the .gitmeta file is tracked + system("git add $gitmeta"); +} + + +sub printstats { + my $path = $_[0]; + $path =~ s/@/\@/g; + my (undef,undef,$mode,undef,$uid,$gid) = lstat($path); + $path =~ s/%/\%/g; + if ($stdout) { + print $path; + printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } + else { + print OUT $path; + printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } +} @@ -80,24 +80,19 @@ static int is_binary(unsigned long size, struct text_stat *stats) return 0; } -static char *crlf_to_git(const char *path, const char *src, unsigned long *sizep, int action) +static int crlf_to_git(const char *path, const char *src, size_t len, + struct strbuf *buf, int action) { - char *buffer, *dst; - unsigned long size, nsize; struct text_stat stats; + char *dst; - if ((action == CRLF_BINARY) || !auto_crlf) - return NULL; - - size = *sizep; - if (!size) - return NULL; - - gather_stats(src, size, &stats); + if ((action == CRLF_BINARY) || !auto_crlf || !len) + return 0; + gather_stats(src, len, &stats); /* No CR? Nothing to convert, regardless. */ if (!stats.cr) - return NULL; + return 0; if (action == CRLF_GUESS) { /* @@ -106,24 +101,19 @@ static char *crlf_to_git(const char *path, const char *src, unsigned long *sizep * stuff? */ if (stats.cr != stats.crlf) - return NULL; + return 0; /* * And add some heuristics for binary vs text, of course... */ - if (is_binary(size, &stats)) - return NULL; + if (is_binary(len, &stats)) + return 0; } - /* - * Ok, allocate a new buffer, fill it in, and return it - * to let the caller know that we switched buffers. - */ - nsize = size - stats.crlf; - buffer = xmalloc(nsize); - *sizep = nsize; - - dst = buffer; + /* only grow if not in place */ + if (strbuf_avail(buf) + buf->len < len) + strbuf_grow(buf, len - buf->len); + dst = buf->buf; if (action == CRLF_GUESS) { /* * If we guessed, we already know we rejected a file with @@ -134,120 +124,112 @@ static char *crlf_to_git(const char *path, const char *src, unsigned long *sizep unsigned char c = *src++; if (c != '\r') *dst++ = c; - } while (--size); + } while (--len); } else { do { unsigned char c = *src++; - if (! (c == '\r' && (1 < size && *src == '\n'))) + if (! (c == '\r' && (1 < len && *src == '\n'))) *dst++ = c; - } while (--size); + } while (--len); } - - return buffer; + strbuf_setlen(buf, dst - buf->buf); + return 1; } -static char *crlf_to_worktree(const char *path, const char *src, unsigned long *sizep, int action) +static int crlf_to_worktree(const char *path, const char *src, size_t len, + struct strbuf *buf, int action) { - char *buffer, *dst; - unsigned long size, nsize; + char *to_free = NULL; struct text_stat stats; - unsigned char last; if ((action == CRLF_BINARY) || (action == CRLF_INPUT) || auto_crlf <= 0) - return NULL; + return 0; - size = *sizep; - if (!size) - return NULL; + if (!len) + return 0; - gather_stats(src, size, &stats); + gather_stats(src, len, &stats); /* No LF? Nothing to convert, regardless. */ if (!stats.lf) - return NULL; + return 0; /* Was it already in CRLF format? */ if (stats.lf == stats.crlf) - return NULL; + return 0; if (action == CRLF_GUESS) { /* If we have any bare CR characters, we're not going to touch it */ if (stats.cr != stats.crlf) - return NULL; + return 0; - if (is_binary(size, &stats)) - return NULL; + if (is_binary(len, &stats)) + return 0; } - /* - * Ok, allocate a new buffer, fill it in, and return it - * to let the caller know that we switched buffers. - */ - nsize = size + stats.lf - stats.crlf; - buffer = xmalloc(nsize); - *sizep = nsize; - last = 0; - - dst = buffer; - do { - unsigned char c = *src++; - if (c == '\n' && last != '\r') - *dst++ = '\r'; - *dst++ = c; - last = c; - } while (--size); - - return buffer; + /* are we "faking" in place editing ? */ + if (src == buf->buf) + to_free = strbuf_detach(buf, NULL); + + strbuf_grow(buf, len + stats.lf - stats.crlf); + for (;;) { + const char *nl = memchr(src, '\n', len); + if (!nl) + break; + if (nl > src && nl[-1] == '\r') { + strbuf_add(buf, src, nl + 1 - src); + } else { + strbuf_add(buf, src, nl - src); + strbuf_addstr(buf, "\r\n"); + } + len -= nl + 1 - src; + src = nl + 1; + } + strbuf_add(buf, src, len); + + free(to_free); + return 1; } -static int filter_buffer(const char *path, const char *src, - unsigned long size, const char *cmd) +struct filter_params { + const char *src; + unsigned long size; + const char *cmd; +}; + +static int filter_buffer(int fd, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. */ struct child_process child_process; - int pipe_feed[2]; + struct filter_params *params = (struct filter_params *)data; int write_err, status; + const char *argv[] = { "sh", "-c", params->cmd, NULL }; memset(&child_process, 0, sizeof(child_process)); + child_process.argv = argv; + child_process.in = -1; + child_process.out = fd; - if (pipe(pipe_feed) < 0) { - error("cannot create pipe to run external filter %s", cmd); - return 1; - } + if (start_command(&child_process)) + return error("cannot fork to run external filter %s", params->cmd); - child_process.pid = fork(); - if (child_process.pid < 0) { - error("cannot fork to run external filter %s", cmd); - close(pipe_feed[0]); - close(pipe_feed[1]); - return 1; - } - if (!child_process.pid) { - dup2(pipe_feed[0], 0); - close(pipe_feed[0]); - close(pipe_feed[1]); - execlp("sh", "sh", "-c", cmd, NULL); - return 1; - } - close(pipe_feed[0]); - - write_err = (write_in_full(pipe_feed[1], src, size) < 0); - if (close(pipe_feed[1])) + write_err = (write_in_full(child_process.in, params->src, params->size) < 0); + if (close(child_process.in)) write_err = 1; if (write_err) - error("cannot feed the input to external filter %s", cmd); + error("cannot feed the input to external filter %s", params->cmd); status = finish_command(&child_process); if (status) - error("external filter %s failed %d", cmd, -status); + error("external filter %s failed %d", params->cmd, -status); return (write_err || status); } -static char *apply_filter(const char *path, const char *src, - unsigned long *sizep, const char *cmd) +static int apply_filter(const char *path, const char *src, size_t len, + struct strbuf *dst, const char *cmd) { /* * Create a pipeline to have the command filter the buffer's @@ -255,77 +237,44 @@ static char *apply_filter(const char *path, const char *src, * * (child --> cmd) --> us */ - const int SLOP = 4096; - int pipe_feed[2]; - int status; - char *dst; - unsigned long dstsize, dstalloc; - struct child_process child_process; + int ret = 1; + struct strbuf nbuf; + struct async async; + struct filter_params params; if (!cmd) - return NULL; + return 0; - memset(&child_process, 0, sizeof(child_process)); - - if (pipe(pipe_feed) < 0) { - error("cannot create pipe to run external filter %s", cmd); - return NULL; - } + memset(&async, 0, sizeof(async)); + async.proc = filter_buffer; + async.data = ¶ms; + params.src = src; + params.size = len; + params.cmd = cmd; fflush(NULL); - child_process.pid = fork(); - if (child_process.pid < 0) { - error("cannot fork to run external filter %s", cmd); - close(pipe_feed[0]); - close(pipe_feed[1]); - return NULL; - } - if (!child_process.pid) { - dup2(pipe_feed[1], 1); - close(pipe_feed[0]); - close(pipe_feed[1]); - exit(filter_buffer(path, src, *sizep, cmd)); - } - close(pipe_feed[1]); - - dstalloc = *sizep; - dst = xmalloc(dstalloc); - dstsize = 0; + if (start_async(&async)) + return 0; /* error was already reported */ - while (1) { - ssize_t numread = xread(pipe_feed[0], dst + dstsize, - dstalloc - dstsize); - - if (numread <= 0) { - if (!numread) - break; - error("read from external filter %s failed", cmd); - free(dst); - dst = NULL; - break; - } - dstsize += numread; - if (dstalloc <= dstsize + SLOP) { - dstalloc = dstsize + SLOP; - dst = xrealloc(dst, dstalloc); - } + strbuf_init(&nbuf, 0); + if (strbuf_read(&nbuf, async.out, len) < 0) { + error("read from external filter %s failed", cmd); + ret = 0; } - if (close(pipe_feed[0])) { + if (close(async.out)) { error("read from external filter %s failed", cmd); - free(dst); - dst = NULL; + ret = 0; } - - status = finish_command(&child_process); - if (status) { - error("external filter %s failed %d", cmd, -status); - free(dst); - dst = NULL; + if (finish_async(&async)) { + error("external filter %s failed", cmd); + ret = 0; } - if (dst) - *sizep = dstsize; - return dst; + if (ret) { + strbuf_swap(dst, &nbuf); + } + strbuf_release(&nbuf); + return ret; } static struct convert_driver { @@ -353,13 +302,8 @@ static int read_convert_config(const char *var, const char *value) if (!strncmp(drv->name, name, namelen) && !drv->name[namelen]) break; if (!drv) { - char *namebuf; drv = xcalloc(1, sizeof(struct convert_driver)); - namebuf = xmalloc(namelen + 1); - memcpy(namebuf, name, namelen); - namebuf[namelen] = 0; - drv->name = namebuf; - drv->next = NULL; + drv->name = xmemdupz(name, namelen); *user_convert_tail = drv; user_convert_tail = &(drv->next); } @@ -449,137 +393,106 @@ static int count_ident(const char *cp, unsigned long size) return cnt; } -static char *ident_to_git(const char *path, const char *src, unsigned long *sizep, int ident) +static int ident_to_git(const char *path, const char *src, size_t len, + struct strbuf *buf, int ident) { - int cnt; - unsigned long size; - char *dst, *buf; + char *dst, *dollar; - if (!ident) - return NULL; - size = *sizep; - cnt = count_ident(src, size); - if (!cnt) - return NULL; - buf = xmalloc(size); - - for (dst = buf; size; size--) { - char ch = *src++; - *dst++ = ch; - if ((ch == '$') && (3 <= size) && - !memcmp("Id:", src, 3)) { - unsigned long rem = size - 3; - const char *cp = src + 3; - do { - ch = *cp++; - if (ch == '$') - break; - rem--; - } while (rem); - if (!rem) - continue; + if (!ident || !count_ident(src, len)) + return 0; + + /* only grow if not in place */ + if (strbuf_avail(buf) + buf->len < len) + strbuf_grow(buf, len - buf->len); + dst = buf->buf; + for (;;) { + dollar = memchr(src, '$', len); + if (!dollar) + break; + memcpy(dst, src, dollar + 1 - src); + dst += dollar + 1 - src; + len -= dollar + 1 - src; + src = dollar + 1; + + if (len > 3 && !memcmp(src, "Id:", 3)) { + dollar = memchr(src + 3, '$', len - 3); + if (!dollar) + break; memcpy(dst, "Id$", 3); dst += 3; - size -= (cp - src); - src = cp; + len -= dollar + 1 - src; + src = dollar + 1; } } - - *sizep = dst - buf; - return buf; + memcpy(dst, src, len); + strbuf_setlen(buf, dst + len - buf->buf); + return 1; } -static char *ident_to_worktree(const char *path, const char *src, unsigned long *sizep, int ident) +static int ident_to_worktree(const char *path, const char *src, size_t len, + struct strbuf *buf, int ident) { - int cnt; - unsigned long size; - char *dst, *buf; unsigned char sha1[20]; + char *to_free = NULL, *dollar; + int cnt; if (!ident) - return NULL; + return 0; - size = *sizep; - cnt = count_ident(src, size); + cnt = count_ident(src, len); if (!cnt) - return NULL; + return 0; - hash_sha1_file(src, size, "blob", sha1); - buf = xmalloc(size + cnt * 43); - - for (dst = buf; size; size--) { - const char *cp; - /* Fetch next source character, move the pointer on */ - char ch = *src++; - /* Copy the current character to the destination */ - *dst++ = ch; - /* If the current character is "$" or there are less than three - * remaining bytes or the two bytes following this one are not - * "Id", then simply read the next character */ - if ((ch != '$') || (size < 3) || memcmp("Id", src, 2)) - continue; - /* - * Here when - * - There are more than 2 bytes remaining - * - The current three bytes are "$Id" - * with - * - ch == "$" - * - src[0] == "I" - */ + /* are we "faking" in place editing ? */ + if (src == buf->buf) + to_free = strbuf_detach(buf, NULL); + hash_sha1_file(src, len, "blob", sha1); - /* - * It's possible that an expanded Id has crept its way into the - * repository, we cope with that by stripping the expansion out - */ - if (src[2] == ':') { - /* Expanded keywords have "$Id:" at the front */ + strbuf_grow(buf, len + cnt * 43); + for (;;) { + /* step 1: run to the next '$' */ + dollar = memchr(src, '$', len); + if (!dollar) + break; + strbuf_add(buf, src, dollar + 1 - src); + len -= dollar + 1 - src; + src = dollar + 1; - /* discard up to but not including the closing $ */ - unsigned long rem = size - 3; - /* Point at first byte after the ":" */ - cp = src + 3; - /* - * Throw away characters until either - * - we reach a "$" - * - we run out of bytes (rem == 0) - */ - do { - ch = *cp; - if (ch == '$') - break; - cp++; - rem--; - } while (rem); - /* If the above finished because it ran out of characters, then - * this is an incomplete keyword, so don't run the expansion */ - if (!rem) - continue; - } else if (src[2] == '$') - cp = src + 2; - else - /* Anything other than "$Id:XXX$" or $Id$ and we skip the - * expansion */ + /* step 2: does it looks like a bit like Id:xxx$ or Id$ ? */ + if (len < 3 || memcmp("Id", src, 2)) continue; - /* cp is now pointing at the last $ of the keyword */ - - memcpy(dst, "Id: ", 4); - dst += 4; - memcpy(dst, sha1_to_hex(sha1), 40); - dst += 40; - *dst++ = ' '; + /* step 3: skip over Id$ or Id:xxxxx$ */ + if (src[2] == '$') { + src += 3; + len -= 3; + } else if (src[2] == ':') { + /* + * It's possible that an expanded Id has crept its way into the + * repository, we cope with that by stripping the expansion out + */ + dollar = memchr(src + 3, '$', len - 3); + if (!dollar) { + /* incomplete keyword, no more '$', so just quit the loop */ + break; + } - /* Adjust for the characters we've discarded */ - size -= (cp - src); - src = cp; + len -= dollar + 1 - src; + src = dollar + 1; + } else { + /* it wasn't a "Id$" or "Id:xxxx$" */ + continue; + } - /* Copy the final "$" */ - *dst++ = *src++; - size--; + /* step 4: substitute */ + strbuf_addstr(buf, "Id: "); + strbuf_add(buf, sha1_to_hex(sha1), 40); + strbuf_addstr(buf, " $"); } + strbuf_add(buf, src, len); - *sizep = dst - buf; - return buf; + free(to_free); + return 1; } static int git_path_check_crlf(const char *path, struct git_attr_check *check) @@ -618,13 +531,12 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check) return !!ATTR_TRUE(value); } -char *convert_to_git(const char *path, const char *src, unsigned long *sizep) +int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst) { struct git_attr_check check[3]; int crlf = CRLF_GUESS; - int ident = 0; + int ident = 0, ret = 0; char *filter = NULL; - char *buf, *buf2; setup_convert_check(check); if (!git_checkattr(path, ARRAY_SIZE(check), check)) { @@ -636,30 +548,25 @@ char *convert_to_git(const char *path, const char *src, unsigned long *sizep) filter = drv->clean; } - buf = apply_filter(path, src, sizep, filter); - - buf2 = crlf_to_git(path, buf ? buf : src, sizep, crlf); - if (buf2) { - free(buf); - buf = buf2; + ret |= apply_filter(path, src, len, dst, filter); + if (ret) { + src = dst->buf; + len = dst->len; } - - buf2 = ident_to_git(path, buf ? buf : src, sizep, ident); - if (buf2) { - free(buf); - buf = buf2; + ret |= crlf_to_git(path, src, len, dst, crlf); + if (ret) { + src = dst->buf; + len = dst->len; } - - return buf; + return ret | ident_to_git(path, src, len, dst, ident); } -char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep) +int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst) { struct git_attr_check check[3]; int crlf = CRLF_GUESS; - int ident = 0; + int ident = 0, ret = 0; char *filter = NULL; - char *buf, *buf2; setup_convert_check(check); if (!git_checkattr(path, ARRAY_SIZE(check), check)) { @@ -671,34 +578,15 @@ char *convert_to_working_tree(const char *path, const char *src, unsigned long * filter = drv->smudge; } - buf = ident_to_worktree(path, src, sizep, ident); - - buf2 = crlf_to_worktree(path, buf ? buf : src, sizep, crlf); - if (buf2) { - free(buf); - buf = buf2; + ret |= ident_to_worktree(path, src, len, dst, ident); + if (ret) { + src = dst->buf; + len = dst->len; } - - buf2 = apply_filter(path, buf ? buf : src, sizep, filter); - if (buf2) { - free(buf); - buf = buf2; - } - - return buf; -} - -void *convert_sha1_file(const char *path, const unsigned char *sha1, - unsigned int mode, enum object_type *type, - unsigned long *size) -{ - void *buffer = read_sha1_file(sha1, type, size); - if (S_ISREG(mode) && buffer) { - void *converted = convert_to_working_tree(path, buffer, size); - if (converted) { - free(buffer); - buffer = converted; - } + ret |= crlf_to_worktree(path, src, len, dst, crlf); + if (ret) { + src = dst->buf; + len = dst->len; } - return buffer; + return ret | apply_filter(path, src, len, dst, filter); } diff --git a/csum-file.c b/csum-file.c index 9ab997120d..9728a99541 100644 --- a/csum-file.c +++ b/csum-file.c @@ -8,6 +8,7 @@ * able to verify hasn't been messed with afterwards. */ #include "cache.h" +#include "progress.h" #include "csum-file.h" static void sha1flush(struct sha1file *f, unsigned int count) @@ -17,6 +18,8 @@ static void sha1flush(struct sha1file *f, unsigned int count) for (;;) { int ret = xwrite(f->fd, buf, count); if (ret > 0) { + f->total += ret; + display_throughput(f->tp, f->total); buf = (char *) buf + ret; count -= ret; if (count) @@ -31,22 +34,27 @@ static void sha1flush(struct sha1file *f, unsigned int count) int sha1close(struct sha1file *f, unsigned char *result, int final) { + int fd; unsigned offset = f->offset; if (offset) { SHA1_Update(&f->ctx, f->buffer, offset); sha1flush(f, offset); f->offset = 0; } - if (!final) - return 0; /* only want to flush (no checksum write, no close) */ - SHA1_Final(f->buffer, &f->ctx); - if (result) - hashcpy(result, f->buffer); - sha1flush(f, 20); - if (close(f->fd)) - die("%s: sha1 file error on close (%s)", f->name, strerror(errno)); + if (final) { + /* write checksum and close fd */ + SHA1_Final(f->buffer, &f->ctx); + if (result) + hashcpy(result, f->buffer); + sha1flush(f, 20); + if (close(f->fd)) + die("%s: sha1 file error on close (%s)", + f->name, strerror(errno)); + fd = 0; + } else + fd = f->fd; free(f); - return 0; + return fd; } int sha1write(struct sha1file *f, void *buf, unsigned int count) @@ -75,20 +83,17 @@ int sha1write(struct sha1file *f, void *buf, unsigned int count) struct sha1file *sha1fd(int fd, const char *name) { - struct sha1file *f; - unsigned len; - - f = xmalloc(sizeof(*f)); - - len = strlen(name); - if (len >= PATH_MAX) - die("you wascally wabbit, you"); - f->namelen = len; - memcpy(f->name, name, len+1); + return sha1fd_throughput(fd, name, NULL); +} +struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp) +{ + struct sha1file *f = xmalloc(sizeof(*f)); f->fd = fd; - f->error = 0; f->offset = 0; + f->total = 0; + f->tp = tp; + f->name = name; f->do_crc = 0; SHA1_Init(&f->ctx); return f; diff --git a/csum-file.h b/csum-file.h index c3c792f1b5..1af76562f3 100644 --- a/csum-file.h +++ b/csum-file.h @@ -1,18 +1,23 @@ #ifndef CSUM_FILE_H #define CSUM_FILE_H +struct progress; + /* A SHA1-protected file */ struct sha1file { - int fd, error; - unsigned int offset, namelen; + int fd; + unsigned int offset; SHA_CTX ctx; - char name[PATH_MAX]; + off_t total; + struct progress *tp; + const char *name; int do_crc; uint32_t crc32; unsigned char buffer[8192]; }; extern struct sha1file *sha1fd(int fd, const char *name); +extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp); extern int sha1close(struct sha1file *, unsigned char *, int); extern int sha1write(struct sha1file *, void *, unsigned int); extern void crc32_begin(struct sha1file *); @@ -406,7 +406,8 @@ static struct daemon_service daemon_service[] = { { "receive-pack", "receivepack", receive_pack, 0, 1 }, }; -static void enable_service(const char *name, int ena) { +static void enable_service(const char *name, int ena) +{ int i; for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { if (!strcmp(daemon_service[i].name, name)) { @@ -417,7 +418,8 @@ static void enable_service(const char *name, int ena) { die("No such service %s", name); } -static void make_service_overridable(const char *name, int ena) { +static void make_service_overridable(const char *name, int ena) +{ int i; for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { if (!strcmp(daemon_service[i].name, name)) { @@ -584,6 +584,26 @@ int parse_date(const char *date, char *result, int maxlen) return date_string(then, offset, result, maxlen); } +enum date_mode parse_date_format(const char *format) +{ + if (!strcmp(format, "relative")) + return DATE_RELATIVE; + else if (!strcmp(format, "iso8601") || + !strcmp(format, "iso")) + return DATE_ISO8601; + else if (!strcmp(format, "rfc2822") || + !strcmp(format, "rfc")) + return DATE_RFC2822; + else if (!strcmp(format, "short")) + return DATE_SHORT; + else if (!strcmp(format, "local")) + return DATE_LOCAL; + else if (!strcmp(format, "default")) + return DATE_NORMAL; + else + die("unknown date format %s", format); +} + void datestamp(char *buf, int bufsize) { time_t now; diff --git a/diff-delta.c b/diff-delta.c index 0dde2f2dc0..9e440a9299 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -115,7 +115,11 @@ static const unsigned int U[256] = { struct index_entry { const unsigned char *ptr; unsigned int val; - struct index_entry *next; +}; + +struct unpacked_index_entry { + struct index_entry entry; + struct unpacked_index_entry *next; }; struct delta_index { @@ -131,7 +135,8 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) unsigned int i, hsize, hmask, entries, prev_val, *hash_count; const unsigned char *data, *buffer = buf; struct delta_index *index; - struct index_entry *entry, **hash; + struct unpacked_index_entry *entry, **hash; + struct index_entry *packed_entry, **packed_hash; void *mem; unsigned long memsize; @@ -148,28 +153,21 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) hmask = hsize - 1; /* allocate lookup index */ - memsize = sizeof(*index) + - sizeof(*hash) * hsize + + memsize = sizeof(*hash) * hsize + sizeof(*entry) * entries; mem = malloc(memsize); if (!mem) return NULL; - index = mem; - mem = index + 1; hash = mem; mem = hash + hsize; entry = mem; - index->memsize = memsize; - index->src_buf = buf; - index->src_size = bufsize; - index->hash_mask = hmask; memset(hash, 0, hsize * sizeof(*hash)); /* allocate an array to count hash entries */ hash_count = calloc(hsize, sizeof(*hash_count)); if (!hash_count) { - free(index); + free(hash); return NULL; } @@ -183,12 +181,13 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; if (val == prev_val) { /* keep the lowest of consecutive identical blocks */ - entry[-1].ptr = data + RABIN_WINDOW; + entry[-1].entry.ptr = data + RABIN_WINDOW; + --entries; } else { prev_val = val; i = val & hmask; - entry->ptr = data + RABIN_WINDOW; - entry->val = val; + entry->entry.ptr = data + RABIN_WINDOW; + entry->entry.val = val; entry->next = hash[i]; hash[i] = entry++; hash_count[i]++; @@ -208,20 +207,84 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) * the reference buffer. */ for (i = 0; i < hsize; i++) { - if (hash_count[i] < HASH_LIMIT) + int acc; + + if (hash_count[i] <= HASH_LIMIT) continue; + + entries -= hash_count[i] - HASH_LIMIT; + /* We leave exactly HASH_LIMIT entries in the bucket */ + entry = hash[i]; + acc = 0; do { - struct index_entry *keep = entry; - int skip = hash_count[i] / HASH_LIMIT; - do { - entry = entry->next; - } while(--skip && entry); - keep->next = entry; - } while(entry); + acc += hash_count[i] - HASH_LIMIT; + if (acc > 0) { + struct unpacked_index_entry *keep = entry; + do { + entry = entry->next; + acc -= HASH_LIMIT; + } while (acc > 0); + keep->next = entry->next; + } + entry = entry->next; + } while (entry); + + /* Assume that this loop is gone through exactly + * HASH_LIMIT times and is entered and left with + * acc==0. So the first statement in the loop + * contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT + * to the accumulator, and the inner loop consequently + * is run (hash_count[i]-HASH_LIMIT) times, removing + * one element from the list each time. Since acc + * balances out to 0 at the final run, the inner loop + * body can't be left with entry==NULL. So we indeed + * encounter entry==NULL in the outer loop only. + */ } free(hash_count); + /* Now create the packed index in array form rather than + * linked lists */ + + memsize = sizeof(*index) + + sizeof(*packed_hash) * (hsize+1) + + sizeof(*packed_entry) * entries; + + mem = malloc(memsize); + + if (!mem) { + free(hash); + return NULL; + } + + index = mem; + index->memsize = memsize; + index->src_buf = buf; + index->src_size = bufsize; + index->hash_mask = hmask; + + mem = index + 1; + packed_hash = mem; + mem = packed_hash + (hsize+1); + packed_entry = mem; + + /* Coalesce all entries belonging to one linked list into + * consecutive array entries */ + + for (i = 0; i < hsize; i++) { + packed_hash[i] = packed_entry; + for (entry = hash[i]; entry; entry = entry->next) + *packed_entry++ = entry->entry; + } + + /* Sentinel value to indicate the length of the last hash + * bucket */ + + packed_hash[hsize] = packed_entry; + assert(packed_entry - (struct index_entry *)mem == entries); + free(hash); + return index; } @@ -302,7 +365,7 @@ create_delta(const struct delta_index *index, val ^= U[data[-RABIN_WINDOW]]; val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; i = val & index->hash_mask; - for (entry = index->hash[i]; entry; entry = entry->next) { + for (entry = index->hash[i]; entry < index->hash[i+1]; entry++) { const unsigned char *ref = entry->ptr; const unsigned char *src = data; unsigned int ref_size = ref_top - ref; diff --git a/diff-lib.c b/diff-lib.c index ec1b5e3d44..d85d8f34ba 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -121,7 +121,7 @@ static int queue_diff(struct diff_options *o, } else { struct diff_filespec *d1, *d2; - if (o->reverse_diff) { + if (DIFF_OPT_TST(o, REVERSE_DIFF)) { unsigned tmp; const char *tmp_c; tmp = mode1; mode1 = mode2; mode2 = tmp; @@ -189,8 +189,8 @@ static int handle_diff_files_args(struct rev_info *revs, else if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--no-index")) { revs->max_count = -2; - revs->diffopt.exit_with_status = 1; - revs->diffopt.no_index = 1; + DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS); + DIFF_OPT_SET(&revs->diffopt, NO_INDEX); } else if (!strcmp(argv[1], "-q")) *options |= DIFF_SILENT_ON_REMOVED; @@ -208,7 +208,7 @@ static int handle_diff_files_args(struct rev_info *revs, if (!is_in_index(revs->diffopt.paths[0]) || !is_in_index(revs->diffopt.paths[1])) { revs->max_count = -2; - revs->diffopt.no_index = 1; + DIFF_OPT_SET(&revs->diffopt, NO_INDEX); } } @@ -231,7 +231,7 @@ static int handle_diff_files_args(struct rev_info *revs, static int is_outside_repo(const char *path, int nongit, const char *prefix) { int i; - if (nongit || !strcmp(path, "-") || path[0] == '/') + if (nongit || !strcmp(path, "-") || is_absolute_path(path)) return 1; if (prefixcmp(path, "../")) return 0; @@ -259,7 +259,7 @@ int setup_diff_no_index(struct rev_info *revs, break; } else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) { i = argc - 3; - revs->diffopt.exit_with_status = 1; + DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS); break; } if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) && @@ -297,7 +297,7 @@ int setup_diff_no_index(struct rev_info *revs, else revs->diffopt.paths = argv + argc - 2; revs->diffopt.nr_paths = 2; - revs->diffopt.no_index = 1; + DIFF_OPT_SET(&revs->diffopt, NO_INDEX); revs->max_count = -2; if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); @@ -311,7 +311,7 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv) if (handle_diff_files_args(revs, argc, argv, &options)) return -1; - if (revs->diffopt.no_index) { + if (DIFF_OPT_TST(&revs->diffopt, NO_INDEX)) { if (revs->diffopt.nr_paths != 2) return error("need two files/directories with --no-index"); if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], @@ -350,7 +350,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; - if (revs->diffopt.quiet && revs->diffopt.has_changes) + if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; if (!ce_path_match(ce, revs->prune_data)) @@ -446,7 +447,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } changed = ce_match_stat(ce, &st, ce_option); - if (!changed && !revs->diffopt.find_copies_harder) + if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) continue; oldmode = ntohl(ce->ce_mode); newmode = ntohl(ce_mode_from_stat(ce, st.st_mode)); @@ -565,7 +566,7 @@ static int show_modified(struct rev_info *revs, oldmode = old->ce_mode; if (mode == oldmode && !hashcmp(sha1, old->sha1) && - !revs->diffopt.find_copies_harder) + !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; mode = ntohl(mode); @@ -585,7 +586,8 @@ static int diff_cache(struct rev_info *revs, struct cache_entry *ce = *ac; int same = (entries > 1) && ce_same_name(ce, ac[1]); - if (revs->diffopt.quiet && revs->diffopt.has_changes) + if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; if (!ce_path_match(ce, pathspec)) @@ -9,6 +9,7 @@ #include "xdiff-interface.h" #include "color.h" #include "attr.h" +#include "run-command.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -83,13 +84,8 @@ static int parse_lldiff_command(const char *var, const char *ep, const char *val if (!strncmp(drv->name, name, namelen) && !drv->name[namelen]) break; if (!drv) { - char *namebuf; drv = xcalloc(1, sizeof(struct ll_diff_driver)); - namebuf = xmalloc(namelen + 1); - memcpy(namebuf, name, namelen); - namebuf[namelen] = 0; - drv->name = namebuf; - drv->next = NULL; + drv->name = xmemdupz(name, namelen); if (!user_diff_tail) user_diff_tail = &user_diff; *user_diff_tail = drv; @@ -126,12 +122,8 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v if (!strncmp(pp->name, name, namelen) && !pp->name[namelen]) break; if (!pp) { - char *namebuf; pp = xcalloc(1, sizeof(*pp)); - namebuf = xmalloc(namelen + 1); - memcpy(namebuf, name, namelen); - namebuf[namelen] = 0; - pp->name = namebuf; + pp->name = xmemdupz(name, namelen); pp->next = funcname_pattern_list; funcname_pattern_list = pp; } @@ -190,44 +182,23 @@ int git_diff_ui_config(const char *var, const char *value) return git_default_config(var, value); } -static char *quote_one(const char *str) -{ - int needlen; - char *xp; - - if (!str) - return NULL; - needlen = quote_c_style(str, NULL, NULL, 0); - if (!needlen) - return xstrdup(str); - xp = xmalloc(needlen + 1); - quote_c_style(str, xp, NULL, 0); - return xp; -} - static char *quote_two(const char *one, const char *two) { int need_one = quote_c_style(one, NULL, NULL, 1); int need_two = quote_c_style(two, NULL, NULL, 1); - char *xp; + struct strbuf res; + strbuf_init(&res, 0); if (need_one + need_two) { - if (!need_one) need_one = strlen(one); - if (!need_two) need_one = strlen(two); - - xp = xmalloc(need_one + need_two + 3); - xp[0] = '"'; - quote_c_style(one, xp + 1, NULL, 1); - quote_c_style(two, xp + need_one + 1, NULL, 1); - strcpy(xp + need_one + need_two + 1, "\""); - return xp; + strbuf_addch(&res, '"'); + quote_c_style(one, &res, NULL, 1); + quote_c_style(two, &res, NULL, 1); + strbuf_addch(&res, '"'); + } else { + strbuf_addstr(&res, one); + strbuf_addstr(&res, two); } - need_one = strlen(one); - need_two = strlen(two); - xp = xmalloc(need_one + need_two + 1); - strcpy(xp, one); - strcpy(xp + need_one, two); - return xp; + return strbuf_detach(&res, NULL); } static const char *external_diff(void) @@ -679,27 +650,20 @@ static char *pprint_rename(const char *a, const char *b) { const char *old = a; const char *new = b; - char *name = NULL; + struct strbuf name; int pfx_length, sfx_length; int len_a = strlen(a); int len_b = strlen(b); + int a_midlen, b_midlen; int qlen_a = quote_c_style(a, NULL, NULL, 0); int qlen_b = quote_c_style(b, NULL, NULL, 0); + strbuf_init(&name, 0); if (qlen_a || qlen_b) { - if (qlen_a) len_a = qlen_a; - if (qlen_b) len_b = qlen_b; - name = xmalloc( len_a + len_b + 5 ); - if (qlen_a) - quote_c_style(a, name, NULL, 0); - else - memcpy(name, a, len_a); - memcpy(name + len_a, " => ", 4); - if (qlen_b) - quote_c_style(b, name + len_a + 4, NULL, 0); - else - memcpy(name + len_a + 4, b, len_b + 1); - return name; + quote_c_style(a, &name, NULL, 0); + strbuf_addstr(&name, " => "); + quote_c_style(b, &name, NULL, 0); + return strbuf_detach(&name, NULL); } /* Find common prefix */ @@ -728,24 +692,26 @@ static char *pprint_rename(const char *a, const char *b) * pfx{sfx-a => sfx-b} * name-a => name-b */ + a_midlen = len_a - pfx_length - sfx_length; + b_midlen = len_b - pfx_length - sfx_length; + if (a_midlen < 0) + a_midlen = 0; + if (b_midlen < 0) + b_midlen = 0; + + strbuf_grow(&name, pfx_length + a_midlen + b_midlen + sfx_length + 7); if (pfx_length + sfx_length) { - int a_midlen = len_a - pfx_length - sfx_length; - int b_midlen = len_b - pfx_length - sfx_length; - if (a_midlen < 0) a_midlen = 0; - if (b_midlen < 0) b_midlen = 0; - - name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7); - sprintf(name, "%.*s{%.*s => %.*s}%s", - pfx_length, a, - a_midlen, a + pfx_length, - b_midlen, b + pfx_length, - a + len_a - sfx_length); + strbuf_add(&name, a, pfx_length); + strbuf_addch(&name, '{'); } - else { - name = xmalloc(len_a + len_b + 5); - sprintf(name, "%s => %s", a, b); + strbuf_add(&name, a + pfx_length, a_midlen); + strbuf_addstr(&name, " => "); + strbuf_add(&name, b + pfx_length, b_midlen); + if (pfx_length + sfx_length) { + strbuf_addch(&name, '}'); + strbuf_add(&name, a + len_a - sfx_length, sfx_length); } - return name; + return strbuf_detach(&name, NULL); } struct diffstat_t { @@ -848,22 +814,23 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } /* Find the longest filename and max number of changes */ - reset = diff_get_color(options->color_diff, DIFF_RESET); - set = diff_get_color(options->color_diff, DIFF_PLAIN); - add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW); - del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD); + reset = diff_get_color_opt(options, DIFF_RESET); + set = diff_get_color_opt(options, DIFF_PLAIN); + add_c = diff_get_color_opt(options, DIFF_FILE_NEW); + del_c = diff_get_color_opt(options, DIFF_FILE_OLD); for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; int change = file->added + file->deleted; if (!file->is_renamed) { /* renames are already quoted by pprint_rename */ - len = quote_c_style(file->name, NULL, NULL, 0); - if (len) { - char *qname = xmalloc(len + 1); - quote_c_style(file->name, qname, NULL, 0); + struct strbuf buf; + strbuf_init(&buf, 0); + if (quote_c_style(file->name, &buf, NULL, 0)) { free(file->name); - file->name = qname; + file->name = strbuf_detach(&buf, NULL); + } else { + strbuf_release(&buf); } } @@ -1001,12 +968,12 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) printf("-\t-\t"); else printf("%d\t%d\t", file->added, file->deleted); - if (options->line_termination && !file->is_renamed && - quote_c_style(file->name, NULL, NULL, 0)) - quote_c_style(file->name, NULL, stdout, 0); - else + if (!file->is_renamed) { + write_name_quoted(file->name, stdout, options->line_termination); + } else { fputs(file->name, stdout); - putchar(options->line_termination); + putchar(options->line_termination); + } } } @@ -1276,8 +1243,8 @@ static void builtin_diff(const char *name_a, mmfile_t mf1, mf2; const char *lbl[2]; char *a_one, *b_two; - const char *set = diff_get_color(o->color_diff, DIFF_METAINFO); - const char *reset = diff_get_color(o->color_diff, DIFF_RESET); + const char *set = diff_get_color_opt(o, DIFF_METAINFO); + const char *reset = diff_get_color_opt(o, DIFF_RESET); a_one = quote_two("a/", name_a + (*name_a == '/')); b_two = quote_two("b/", name_b + (*name_b == '/')); @@ -1310,7 +1277,7 @@ static void builtin_diff(const char *name_a, goto free_ab_and_return; if (complete_rewrite) { emit_rewrite_diff(name_a, name_b, one, two, - o->color_diff); + DIFF_OPT_TST(o, COLOR_DIFF)); o->found_changes = 1; goto free_ab_and_return; } @@ -1319,13 +1286,13 @@ static void builtin_diff(const char *name_a, if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); - if (!o->text && + if (!DIFF_OPT_TST(o, TEXT) && (diff_filespec_is_binary(one) || diff_filespec_is_binary(two))) { /* Quite common confusing case */ if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) goto free_ab_and_return; - if (o->binary) + if (DIFF_OPT_TST(o, BINARY)) emit_binary_diff(&mf1, &mf2); else printf("Binary files %s and %s differ\n", @@ -1348,7 +1315,7 @@ static void builtin_diff(const char *name_a, memset(&xecfg, 0, sizeof(xecfg)); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.label_path = lbl; - ecbdata.color_diff = o->color_diff; + ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1364,11 +1331,11 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; - if (o->color_diff_words) + if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if (o->color_diff_words) + if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); } @@ -1442,7 +1409,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.xm.consume = checkdiff_consume; data.filename = name_b ? name_b : name_a; data.lineno = 0; - data.color_diff = o->color_diff; + data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1474,9 +1441,18 @@ struct diff_filespec *alloc_filespec(const char *path) memset(spec, 0, sizeof(*spec)); spec->path = (char *)(spec + 1); memcpy(spec->path, path, namelen+1); + spec->count = 1; return spec; } +void free_filespec(struct diff_filespec *spec) +{ + if (!--spec->count) { + diff_free_filespec_data(spec); + free(spec); + } +} + void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, unsigned short mode) { @@ -1545,25 +1521,16 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int static int populate_from_stdin(struct diff_filespec *s) { -#define INCREMENT 1024 - char *buf; - unsigned long size; - ssize_t got; - - size = 0; - buf = NULL; - while (1) { - buf = xrealloc(buf, size + INCREMENT); - got = xread(0, buf + size, INCREMENT); - if (!got) - break; /* EOF */ - if (got < 0) - return error("error while reading from stdin %s", + struct strbuf buf; + size_t size = 0; + + strbuf_init(&buf, 0); + if (strbuf_read(&buf, 0, 0) < 0) + return error("error while reading from stdin %s", strerror(errno)); - size += got; - } + s->should_munmap = 0; - s->data = buf; + s->data = strbuf_detach(&buf, &size); s->size = size; s->should_free = 1; return 0; @@ -1609,10 +1576,9 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) if (!s->sha1_valid || reuse_worktree_file(s->path, s->sha1, 0)) { + struct strbuf buf; struct stat st; int fd; - char *buf; - unsigned long size; if (!strcmp(s->path, "-")) return populate_from_stdin(s); @@ -1653,12 +1619,12 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) /* * Convert from working tree format to canonical git format */ - size = s->size; - buf = convert_to_git(s->path, s->data, &size); - if (buf) { + strbuf_init(&buf, 0); + if (convert_to_git(s->path, s->data, s->size, &buf)) { + size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; - s->data = buf; + s->data = strbuf_detach(&buf, &size); s->size = size; s->should_free = 1; } @@ -1796,40 +1762,6 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } -static int spawn_prog(const char *pgm, const char **arg) -{ - pid_t pid; - int status; - - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - execvp(pgm, (char *const*) arg); - exit(255); - } - - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } - - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. - */ - if (WIFEXITED(status) && !WEXITSTATUS(status)) - return 0; - return -1; -} - /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -1882,7 +1814,8 @@ static void run_external_diff(const char *pgm, *arg++ = name; } *arg = NULL; - retval = spawn_prog(pgm, spawn_arg); + fflush(NULL); + retval = run_command_v_opt(spawn_arg, 0); remove_tempfile(); if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); @@ -1920,7 +1853,7 @@ static void run_diff_cmd(const char *pgm, struct diff_options *o, int complete_rewrite) { - if (!o->allow_external) + if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; else { const char *cmd = external_diff_attr(name); @@ -1967,50 +1900,46 @@ static int similarity_index(struct diff_filepair *p) static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); - char msg[PATH_MAX*2+300], *xfrm_msg; - struct diff_filespec *one; - struct diff_filespec *two; + struct strbuf msg; + char *xfrm_msg; + struct diff_filespec *one = p->one; + struct diff_filespec *two = p->two; const char *name; const char *other; - char *name_munged, *other_munged; int complete_rewrite = 0; - int len; + if (DIFF_PAIR_UNMERGED(p)) { - /* unmerged */ run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0); return; } - name = p->one->path; + name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); - name_munged = quote_one(name); - other_munged = quote_one(other); - one = p->one; two = p->two; - diff_fill_sha1_info(one); diff_fill_sha1_info(two); - len = 0; + strbuf_init(&msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "copy from %s\n" - "copy to %s\n", - similarity_index(p), name_munged, other_munged); + strbuf_addf(&msg, "similarity index %d%%", similarity_index(p)); + strbuf_addstr(&msg, "\ncopy from "); + quote_c_style(name, &msg, NULL, 0); + strbuf_addstr(&msg, "\ncopy to "); + quote_c_style(other, &msg, NULL, 0); + strbuf_addch(&msg, '\n'); break; case DIFF_STATUS_RENAMED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "rename from %s\n" - "rename to %s\n", - similarity_index(p), name_munged, other_munged); + strbuf_addf(&msg, "similarity index %d%%", similarity_index(p)); + strbuf_addstr(&msg, "\nrename from "); + quote_c_style(name, &msg, NULL, 0); + strbuf_addstr(&msg, "\nrename to "); + quote_c_style(other, &msg, NULL, 0); + strbuf_addch(&msg, '\n'); break; case DIFF_STATUS_MODIFIED: if (p->score) { - len += snprintf(msg + len, sizeof(msg) - len, - "dissimilarity index %d%%\n", + strbuf_addf(&msg, "dissimilarity index %d%%\n", similarity_index(p)); complete_rewrite = 1; break; @@ -2022,27 +1951,25 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) } if (hashcmp(one->sha1, two->sha1)) { - int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; + int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; - if (o->binary) { + if (DIFF_OPT_TST(o, BINARY)) { mmfile_t mf; if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) || (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - len += snprintf(msg + len, sizeof(msg) - len, - "index %.*s..%.*s", + strbuf_addf(&msg, "index %.*s..%.*s", abbrev, sha1_to_hex(one->sha1), abbrev, sha1_to_hex(two->sha1)); if (one->mode == two->mode) - len += snprintf(msg + len, sizeof(msg) - len, - " %06o", one->mode); - len += snprintf(msg + len, sizeof(msg) - len, "\n"); + strbuf_addf(&msg, " %06o", one->mode); + strbuf_addch(&msg, '\n'); } - if (len) - msg[--len] = 0; - xfrm_msg = len ? msg : NULL; + if (msg.len) + strbuf_setlen(&msg, msg.len - 1); + xfrm_msg = msg.len ? msg.buf : NULL; if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && @@ -2061,8 +1988,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o, complete_rewrite); - free(name_munged); - free(other_munged); + strbuf_release(&msg); } static void run_diffstat(struct diff_filepair *p, struct diff_options *o, @@ -2119,7 +2045,10 @@ void diff_setup(struct diff_options *options) options->change = diff_change; options->add_remove = diff_addremove; - options->color_diff = diff_use_color_default; + if (diff_use_color_default) + DIFF_OPT_SET(options, COLOR_DIFF); + else + DIFF_OPT_CLR(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; } @@ -2138,7 +2067,7 @@ int diff_setup_done(struct diff_options *options) if (count > 1) die("--name-only, --name-status, --check and -s are mutually exclusive"); - if (options->find_copies_harder) + if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; if (options->output_format & (DIFF_FORMAT_NAME | @@ -2162,12 +2091,12 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) - options->recursive = 1; + DIFF_OPT_SET(options, RECURSIVE); /* * Also pickaxe would not work very well if you do not say recursive */ if (options->pickaxe) - options->recursive = 1; + DIFF_OPT_SET(options, RECURSIVE); if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; @@ -2189,9 +2118,9 @@ int diff_setup_done(struct diff_options *options) * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (options->quiet) { + if (DIFF_OPT_TST(options, QUIET)) { options->output_format = DIFF_FORMAT_NO_OUTPUT; - options->exit_with_status = 1; + DIFF_OPT_SET(options, EXIT_WITH_STATUS); } /* @@ -2199,7 +2128,7 @@ int diff_setup_done(struct diff_options *options) * upon the first hit. We need to run diff as usual. */ if (options->pickaxe || options->filter) - options->quiet = 0; + DIFF_OPT_CLR(options, QUIET); return 0; } @@ -2256,21 +2185,32 @@ static int diff_scoreopt_parse(const char *opt); int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; + + /* Output format options */ if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) options->output_format |= DIFF_FORMAT_PATCH; else if (opt_arg(arg, 'U', "unified", &options->context)) options->output_format |= DIFF_FORMAT_PATCH; else if (!strcmp(arg, "--raw")) options->output_format |= DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--patch-with-raw")) { + else if (!strcmp(arg, "--patch-with-raw")) options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; - } - else if (!strcmp(arg, "--numstat")) { + else if (!strcmp(arg, "--numstat")) options->output_format |= DIFF_FORMAT_NUMSTAT; - } - else if (!strcmp(arg, "--shortstat")) { + else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; - } + else if (!strcmp(arg, "--check")) + options->output_format |= DIFF_FORMAT_CHECKDIFF; + else if (!strcmp(arg, "--summary")) + options->output_format |= DIFF_FORMAT_SUMMARY; + else if (!strcmp(arg, "--patch-with-stat")) + options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT; + else if (!strcmp(arg, "--name-only")) + options->output_format |= DIFF_FORMAT_NAME; + else if (!strcmp(arg, "--name-status")) + options->output_format |= DIFF_FORMAT_NAME_STATUS; + else if (!strcmp(arg, "-s")) + options->output_format |= DIFF_FORMAT_NO_OUTPUT; else if (!prefixcmp(arg, "--stat")) { char *end; int width = options->stat_width; @@ -2298,99 +2238,89 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->stat_name_width = name_width; options->stat_width = width; } - else if (!strcmp(arg, "--check")) - options->output_format |= DIFF_FORMAT_CHECKDIFF; - else if (!strcmp(arg, "--summary")) - options->output_format |= DIFF_FORMAT_SUMMARY; - else if (!strcmp(arg, "--patch-with-stat")) { - options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT; - } - else if (!strcmp(arg, "-z")) - options->line_termination = 0; - else if (!prefixcmp(arg, "-l")) - options->rename_limit = strtoul(arg+2, NULL, 10); - else if (!strcmp(arg, "--full-index")) - options->full_index = 1; - else if (!strcmp(arg, "--binary")) { - options->output_format |= DIFF_FORMAT_PATCH; - options->binary = 1; - } - else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) { - options->text = 1; - } - else if (!strcmp(arg, "--name-only")) - options->output_format |= DIFF_FORMAT_NAME; - else if (!strcmp(arg, "--name-status")) - options->output_format |= DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-R")) - options->reverse_diff = 1; - else if (!prefixcmp(arg, "-S")) - options->pickaxe = arg + 2; - else if (!strcmp(arg, "-s")) { - options->output_format |= DIFF_FORMAT_NO_OUTPUT; - } - else if (!prefixcmp(arg, "-O")) - options->orderfile = arg + 2; - else if (!prefixcmp(arg, "--diff-filter=")) - options->filter = arg + 14; - else if (!strcmp(arg, "--pickaxe-all")) - options->pickaxe_opts = DIFF_PICKAXE_ALL; - else if (!strcmp(arg, "--pickaxe-regex")) - options->pickaxe_opts = DIFF_PICKAXE_REGEX; + + /* renames options */ else if (!prefixcmp(arg, "-B")) { - if ((options->break_opt = - diff_scoreopt_parse(arg)) == -1) + if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return -1; } else if (!prefixcmp(arg, "-M")) { - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) + if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return -1; options->detect_rename = DIFF_DETECT_RENAME; } else if (!prefixcmp(arg, "-C")) { if (options->detect_rename == DIFF_DETECT_COPY) - options->find_copies_harder = 1; - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) + DIFF_OPT_SET(options, FIND_COPIES_HARDER); + if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return -1; options->detect_rename = DIFF_DETECT_COPY; } - else if (!strcmp(arg, "--find-copies-harder")) - options->find_copies_harder = 1; - else if (!strcmp(arg, "--follow")) - options->follow_renames = 1; - else if (!strcmp(arg, "--abbrev")) - options->abbrev = DEFAULT_ABBREV; - else if (!prefixcmp(arg, "--abbrev=")) { - options->abbrev = strtoul(arg + 9, NULL, 10); - if (options->abbrev < MINIMUM_ABBREV) - options->abbrev = MINIMUM_ABBREV; - else if (40 < options->abbrev) - options->abbrev = 40; - } - else if (!strcmp(arg, "--color")) - options->color_diff = 1; - else if (!strcmp(arg, "--no-color")) - options->color_diff = 0; + else if (!strcmp(arg, "--no-renames")) + options->detect_rename = 0; + + /* xdiff options */ else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(arg, "--ignore-space-at-eol")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL; + + /* flags options */ + else if (!strcmp(arg, "--binary")) { + options->output_format |= DIFF_FORMAT_PATCH; + DIFF_OPT_SET(options, BINARY); + } + else if (!strcmp(arg, "--full-index")) + DIFF_OPT_SET(options, FULL_INDEX); + else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) + DIFF_OPT_SET(options, TEXT); + else if (!strcmp(arg, "-R")) + DIFF_OPT_SET(options, REVERSE_DIFF); + else if (!strcmp(arg, "--find-copies-harder")) + DIFF_OPT_SET(options, FIND_COPIES_HARDER); + else if (!strcmp(arg, "--follow")) + DIFF_OPT_SET(options, FOLLOW_RENAMES); + else if (!strcmp(arg, "--color")) + DIFF_OPT_SET(options, COLOR_DIFF); + else if (!strcmp(arg, "--no-color")) + DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) - options->color_diff = options->color_diff_words = 1; - else if (!strcmp(arg, "--no-renames")) - options->detect_rename = 0; + options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS; else if (!strcmp(arg, "--exit-code")) - options->exit_with_status = 1; + DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) - options->quiet = 1; + DIFF_OPT_SET(options, QUIET); else if (!strcmp(arg, "--ext-diff")) - options->allow_external = 1; + DIFF_OPT_SET(options, ALLOW_EXTERNAL); else if (!strcmp(arg, "--no-ext-diff")) - options->allow_external = 0; + DIFF_OPT_CLR(options, ALLOW_EXTERNAL); + + /* misc options */ + else if (!strcmp(arg, "-z")) + options->line_termination = 0; + else if (!prefixcmp(arg, "-l")) + options->rename_limit = strtoul(arg+2, NULL, 10); + else if (!prefixcmp(arg, "-S")) + options->pickaxe = arg + 2; + else if (!strcmp(arg, "--pickaxe-all")) + options->pickaxe_opts = DIFF_PICKAXE_ALL; + else if (!strcmp(arg, "--pickaxe-regex")) + options->pickaxe_opts = DIFF_PICKAXE_REGEX; + else if (!prefixcmp(arg, "-O")) + options->orderfile = arg + 2; + else if (!prefixcmp(arg, "--diff-filter=")) + options->filter = arg + 14; + else if (!strcmp(arg, "--abbrev")) + options->abbrev = DEFAULT_ABBREV; + else if (!prefixcmp(arg, "--abbrev=")) { + options->abbrev = strtoul(arg + 9, NULL, 10); + if (options->abbrev < MINIMUM_ABBREV) + options->abbrev = MINIMUM_ABBREV; + else if (40 < options->abbrev) + options->abbrev = 40; + } else return 0; return 1; @@ -2486,10 +2416,8 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, void diff_free_filepair(struct diff_filepair *p) { - diff_free_filespec_data(p->one); - diff_free_filespec_data(p->two); - free(p->one); - free(p->two); + free_filespec(p->one); + free_filespec(p->two); free(p); } @@ -2518,72 +2446,30 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len) return sha1_to_hex(sha1); } -static void diff_flush_raw(struct diff_filepair *p, - struct diff_options *options) +static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) { - int two_paths; - char status[10]; - int abbrev = options->abbrev; - const char *path_one, *path_two; - int inter_name_termination = '\t'; - int line_termination = options->line_termination; + int line_termination = opt->line_termination; + int inter_name_termination = line_termination ? '\t' : '\0'; - if (!line_termination) - inter_name_termination = 0; - - path_one = p->one->path; - path_two = p->two->path; - if (line_termination) { - path_one = quote_one(path_one); - path_two = quote_one(path_two); - } - - if (p->score) - sprintf(status, "%c%03d", p->status, similarity_index(p)); - else { - status[0] = p->status; - status[1] = 0; - } - switch (p->status) { - case DIFF_STATUS_COPIED: - case DIFF_STATUS_RENAMED: - two_paths = 1; - break; - case DIFF_STATUS_ADDED: - case DIFF_STATUS_DELETED: - two_paths = 0; - break; - default: - two_paths = 0; - break; + if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { + printf(":%06o %06o %s ", p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, opt->abbrev)); + printf("%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); } - if (!(options->output_format & DIFF_FORMAT_NAME_STATUS)) { - printf(":%06o %06o %s ", - p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->sha1, abbrev)); - printf("%s ", - diff_unique_abbrev(p->two->sha1, abbrev)); + if (p->score) { + printf("%c%03d%c", p->status, similarity_index(p), + inter_name_termination); + } else { + printf("%c%c", p->status, inter_name_termination); } - printf("%s%c%s", status, inter_name_termination, - two_paths || p->one->mode ? path_one : path_two); - if (two_paths) - printf("%c%s", inter_name_termination, path_two); - putchar(line_termination); - if (path_one != p->one->path) - free((void*)path_one); - if (path_two != p->two->path) - free((void*)path_two); -} -static void diff_flush_name(struct diff_filepair *p, struct diff_options *opt) -{ - char *path = p->two->path; - - if (opt->line_termination) - path = quote_one(p->two->path); - printf("%s%c", path, opt->line_termination); - if (p->two->path != path) - free(path); + if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) { + write_name_quoted(p->one->path, stdout, inter_name_termination); + write_name_quoted(p->two->path, stdout, line_termination); + } else { + const char *path = p->one->mode ? p->one->path : p->two->path; + write_name_quoted(path, stdout, line_termination); + } } int diff_unmodified_pair(struct diff_filepair *p) @@ -2593,14 +2479,11 @@ int diff_unmodified_pair(struct diff_filepair *p) * let transformers to produce diff_filepairs any way they want, * and filter and clean them up here before producing the output. */ - struct diff_filespec *one, *two; + struct diff_filespec *one = p->one, *two = p->two; if (DIFF_PAIR_UNMERGED(p)) return 0; /* unmerged is interesting */ - one = p->one; - two = p->two; - /* deletion, addition, mode or type change * and rename are all interesting. */ @@ -2686,9 +2569,9 @@ void diff_debug_filepair(const struct diff_filepair *p, int i) { diff_debug_filespec(p->one, i, "one"); diff_debug_filespec(p->two, i, "two"); - fprintf(stderr, "score %d, status %c stays %d broken %d\n", + fprintf(stderr, "score %d, status %c rename_used %d broken %d\n", p->score, p->status ? p->status : '?', - p->source_stays, p->broken_pair); + p->one->rename_used, p->broken_pair); } void diff_debug_queue(const char *msg, struct diff_queue_struct *q) @@ -2706,8 +2589,8 @@ void diff_debug_queue(const char *msg, struct diff_queue_struct *q) static void diff_resolve_rename_copy(void) { - int i, j; - struct diff_filepair *p, *pp; + int i; + struct diff_filepair *p; struct diff_queue_struct *q = &diff_queued_diff; diff_debug_queue("resolve-rename-copy", q); @@ -2729,27 +2612,21 @@ static void diff_resolve_rename_copy(void) * either in-place edit or rename/copy edit. */ else if (DIFF_PAIR_RENAME(p)) { - if (p->source_stays) { - p->status = DIFF_STATUS_COPIED; - continue; - } - /* See if there is some other filepair that - * copies from the same source as us. If so - * we are a copy. Otherwise we are either a - * copy if the path stays, or a rename if it - * does not, but we already handled "stays" case. + /* + * A rename might have re-connected a broken + * pair up, causing the pathnames to be the + * same again. If so, that's not a rename at + * all, just a modification.. + * + * Otherwise, see if this source was used for + * multiple renames, in which case we decrement + * the count, and call it a copy. */ - for (j = i + 1; j < q->nr; j++) { - pp = q->queue[j]; - if (strcmp(pp->one->path, p->one->path)) - continue; /* not us */ - if (!DIFF_PAIR_RENAME(pp)) - continue; /* not a rename/copy */ - /* pp is a rename/copy from the same source */ + if (!strcmp(p->one->path, p->two->path)) + p->status = DIFF_STATUS_MODIFIED; + else if (--p->one->rename_used > 0) p->status = DIFF_STATUS_COPIED; - break; - } - if (!p->status) + else p->status = DIFF_STATUS_RENAMED; } else if (hashcmp(p->one->sha1, p->two->sha1) || @@ -2789,32 +2666,27 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) diff_flush_raw(p, opt); else if (fmt & DIFF_FORMAT_NAME) - diff_flush_name(p, opt); + write_name_quoted(p->two->path, stdout, opt->line_termination); } static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs) { - char *name = quote_one(fs->path); if (fs->mode) - printf(" %s mode %06o %s\n", newdelete, fs->mode, name); + printf(" %s mode %06o ", newdelete, fs->mode); else - printf(" %s %s\n", newdelete, name); - free(name); + printf(" %s ", newdelete); + write_name_quoted(fs->path, stdout, '\n'); } static void show_mode_change(struct diff_filepair *p, int show_name) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { + printf(" mode change %06o => %06o%c", p->one->mode, p->two->mode, + show_name ? ' ' : '\n'); if (show_name) { - char *name = quote_one(p->two->path); - printf(" mode change %06o => %06o %s\n", - p->one->mode, p->two->mode, name); - free(name); + write_name_quoted(p->two->path, stdout, '\n'); } - else - printf(" mode change %06o => %06o\n", - p->one->mode, p->two->mode); } } @@ -2844,12 +2716,11 @@ static void diff_summary(struct diff_filepair *p) break; default: if (p->score) { - char *name = quote_one(p->two->path); - printf(" rewrite %s (%d%%)\n", name, - similarity_index(p)); - free(name); - show_mode_change(p, 0); - } else show_mode_change(p, 1); + fputs(" rewrite ", stdout); + write_name_quoted(p->two->path, stdout, ' '); + printf("(%d%%)\n", similarity_index(p)); + } + show_mode_change(p, !p->score); break; } } @@ -2863,14 +2734,14 @@ struct patch_id_t { static int remove_space(char *line, int len) { int i; - char *dst = line; - unsigned char c; + char *dst = line; + unsigned char c; - for (i = 0; i < len; i++) - if (!isspace((c = line[i]))) - *dst++ = c; + for (i = 0; i < len; i++) + if (!isspace((c = line[i]))) + *dst++ = c; - return dst - line; + return dst - line; } static void patch_id_consume(void *priv, char *line, unsigned long len) @@ -3204,7 +3075,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) * to determine how many paths were dirty only * due to stat info mismatch. */ - if (!diffopt->no_index) + if (!DIFF_OPT_TST(diffopt, NO_INDEX)) diffopt->skip_stat_unmatch++; diff_free_filepair(p); } @@ -3215,10 +3086,10 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) void diffcore_std(struct diff_options *options) { - if (options->quiet) + if (DIFF_OPT_TST(options, QUIET)) return; - if (options->skip_stat_unmatch && !options->find_copies_harder) + if (options->skip_stat_unmatch && !DIFF_OPT_TST(options, FIND_COPIES_HARDER)) diffcore_skip_stat_unmatch(options); if (options->break_opt != -1) diffcore_break(options->break_opt); @@ -3233,7 +3104,10 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); - options->has_changes = !!diff_queued_diff.nr; + if (diff_queued_diff.nr) + DIFF_OPT_SET(options, HAS_CHANGES); + else + DIFF_OPT_CLR(options, HAS_CHANGES); } @@ -3257,7 +3131,7 @@ void diff_addremove(struct diff_options *options, * Before the final output happens, they are pruned after * merged into rename/copy pairs as appropriate. */ - if (options->reverse_diff) + if (DIFF_OPT_TST(options, REVERSE_DIFF)) addremove = (addremove == '+' ? '-' : addremove == '-' ? '+' : addremove); @@ -3272,7 +3146,7 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); - options->has_changes = 1; + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_change(struct diff_options *options, @@ -3284,7 +3158,7 @@ void diff_change(struct diff_options *options, char concatpath[PATH_MAX]; struct diff_filespec *one, *two; - if (options->reverse_diff) { + if (DIFF_OPT_TST(options, REVERSE_DIFF)) { unsigned tmp; const unsigned char *tmp_c; tmp = old_mode; old_mode = new_mode; new_mode = tmp; @@ -3298,7 +3172,7 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_sha1, new_mode); diff_queue(&diff_queued_diff, one, two); - options->has_changes = 1; + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_unmerge(struct diff_options *options, @@ -43,26 +43,32 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_CALLBACK 0x1000 +#define DIFF_OPT_RECURSIVE (1 << 0) +#define DIFF_OPT_TREE_IN_RECURSIVE (1 << 1) +#define DIFF_OPT_BINARY (1 << 2) +#define DIFF_OPT_TEXT (1 << 3) +#define DIFF_OPT_FULL_INDEX (1 << 4) +#define DIFF_OPT_SILENT_ON_REMOVE (1 << 5) +#define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) +#define DIFF_OPT_FOLLOW_RENAMES (1 << 7) +#define DIFF_OPT_COLOR_DIFF (1 << 8) +#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) +#define DIFF_OPT_HAS_CHANGES (1 << 10) +#define DIFF_OPT_QUIET (1 << 11) +#define DIFF_OPT_NO_INDEX (1 << 12) +#define DIFF_OPT_ALLOW_EXTERNAL (1 << 13) +#define DIFF_OPT_EXIT_WITH_STATUS (1 << 14) +#define DIFF_OPT_REVERSE_DIFF (1 << 15) +#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) +#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) +#define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) + struct diff_options { const char *filter; const char *orderfile; const char *pickaxe; const char *single_follow; - unsigned recursive:1, - tree_in_recursive:1, - binary:1, - text:1, - full_index:1, - silent_on_remove:1, - find_copies_harder:1, - follow_renames:1, - color_diff:1, - color_diff_words:1, - has_changes:1, - quiet:1, - no_index:1, - allow_external:1, - exit_with_status:1; + unsigned flags; int context; int break_opt; int detect_rename; @@ -71,7 +77,6 @@ struct diff_options { int output_format; int pickaxe_opts; int rename_score; - int reverse_diff; int rename_limit; int setup; int abbrev; @@ -105,6 +110,9 @@ enum color_diff { DIFF_WHITESPACE = 7, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); +#define diff_get_color_opt(o, ix) \ + diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix) + extern const char mime_boundary_leader[]; diff --git a/diffcore-delta.c b/diffcore-delta.c index d9729e5ec2..e670f85125 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -46,22 +46,6 @@ struct spanhash_top { struct spanhash data[FLEX_ARRAY]; }; -static struct spanhash *spanhash_find(struct spanhash_top *top, - unsigned int hashval) -{ - int sz = 1 << top->alloc_log2; - int bucket = hashval & (sz - 1); - while (1) { - struct spanhash *h = &(top->data[bucket++]); - if (!h->cnt) - return NULL; - if (h->hashval == hashval) - return h; - if (sz <= bucket) - bucket = 0; - } -} - static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig) { struct spanhash_top *new; @@ -122,6 +106,20 @@ static struct spanhash_top *add_spanhash(struct spanhash_top *top, } } +static int spanhash_cmp(const void *a_, const void *b_) +{ + const struct spanhash *a = a_; + const struct spanhash *b = b_; + + /* A count of zero compares at the end.. */ + if (!a->cnt) + return !b->cnt ? 0 : 1; + if (!b->cnt) + return -1; + return a->hashval < b->hashval ? -1 : + a->hashval > b->hashval ? 1 : 0; +} + static struct spanhash_top *hash_chars(struct diff_filespec *one) { int i, n; @@ -158,6 +156,10 @@ static struct spanhash_top *hash_chars(struct diff_filespec *one) n = 0; accum1 = accum2 = 0; } + qsort(hash->data, + 1ul << hash->alloc_log2, + sizeof(hash->data[0]), + spanhash_cmp); return hash; } @@ -169,7 +171,7 @@ int diffcore_count_changes(struct diff_filespec *src, unsigned long *src_copied, unsigned long *literal_added) { - int i, ssz; + struct spanhash *s, *d; struct spanhash_top *src_count, *dst_count; unsigned long sc, la; @@ -190,22 +192,26 @@ int diffcore_count_changes(struct diff_filespec *src, } sc = la = 0; - ssz = 1 << src_count->alloc_log2; - for (i = 0; i < ssz; i++) { - struct spanhash *s = &(src_count->data[i]); - struct spanhash *d; + s = src_count->data; + d = dst_count->data; + for (;;) { unsigned dst_cnt, src_cnt; if (!s->cnt) - continue; + break; /* we checked all in src */ + while (d->cnt) { + if (d->hashval >= s->hashval) + break; + d++; + } src_cnt = s->cnt; - d = spanhash_find(dst_count, s->hashval); - dst_cnt = d ? d->cnt : 0; + dst_cnt = d->hashval == s->hashval ? d->cnt : 0; if (src_cnt < dst_cnt) { la += dst_cnt - src_cnt; sc += src_cnt; } else sc += dst_cnt; + s++; } if (!src_count_p) diff --git a/diffcore-order.c b/diffcore-order.c index 2a4bd8232e..23e93852d8 100644 --- a/diffcore-order.c +++ b/diffcore-order.c @@ -48,11 +48,8 @@ static void prepare_order(const char *orderfile) if (*ep == '\n') { *ep = 0; order[cnt] = cp; - } - else { - order[cnt] = xmalloc(ep-cp+1); - memcpy(order[cnt], cp, ep-cp); - order[cnt][ep-cp] = 0; + } else { + order[cnt] = xmemdupz(cp, ep - cp); } cnt++; } diff --git a/diffcore-rename.c b/diffcore-rename.c index 142e5376dd..f9ebea5640 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,6 +4,7 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "hash.h" /* Table of rename/copy destinations */ @@ -55,12 +56,10 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, static struct diff_rename_src { struct diff_filespec *one; unsigned short score; /* to remember the break score */ - unsigned src_path_left : 1; } *rename_src; static int rename_src_nr, rename_src_alloc; static struct diff_rename_src *register_rename_src(struct diff_filespec *one, - int src_path_left, unsigned short score) { int first, last; @@ -92,33 +91,9 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one, (rename_src_nr - first - 1) * sizeof(*rename_src)); rename_src[first].one = one; rename_src[first].score = score; - rename_src[first].src_path_left = src_path_left; return &(rename_src[first]); } -static int is_exact_match(struct diff_filespec *src, - struct diff_filespec *dst, - int contents_too) -{ - if (src->sha1_valid && dst->sha1_valid && - !hashcmp(src->sha1, dst->sha1)) - return 1; - if (!contents_too) - return 0; - if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1)) - return 0; - if (src->size != dst->size) - return 0; - if (src->sha1_valid && dst->sha1_valid) - return !hashcmp(src->sha1, dst->sha1); - if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) - return 0; - if (src->size == dst->size && - !memcmp(src->data, dst->data, src->size)) - return 1; - return 0; -} - static int basename_same(struct diff_filespec *src, struct diff_filespec *dst) { int src_len = strlen(src->path), dst_len = strlen(dst->path); @@ -169,6 +144,20 @@ static int estimate_similarity(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; + /* + * Need to check that source and destination sizes are + * filled in before comparing them. + * + * If we already have "cnt_data" filled in, we know it's + * all good (avoid checking the size for zero, as that + * is a possible size - we really should have a flag to + * say whether the size is valid or not!) + */ + if (!src->cnt_data && diff_populate_filespec(src, 0)) + return 0; + if (!dst->cnt_data && diff_populate_filespec(dst, 0)) + return 0; + max_size = ((src->size > dst->size) ? src->size : dst->size); base_size = ((src->size < dst->size) ? src->size : dst->size); delta_size = max_size - base_size; @@ -184,11 +173,6 @@ static int estimate_similarity(struct diff_filespec *src, if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; - if ((!src->cnt_data && diff_populate_filespec(src, 0)) - || (!dst->cnt_data && diff_populate_filespec(dst, 0))) - return 0; /* error but caught downstream */ - - delta_limit = (unsigned long) (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE); if (diffcore_count_changes(src, dst, @@ -209,27 +193,25 @@ static int estimate_similarity(struct diff_filespec *src, static void record_rename_pair(int dst_index, int src_index, int score) { - struct diff_filespec *one, *two, *src, *dst; + struct diff_filespec *src, *dst; struct diff_filepair *dp; if (rename_dst[dst_index].pair) die("internal error: dst already matched."); src = rename_src[src_index].one; - one = alloc_filespec(src->path); - fill_filespec(one, src->sha1, src->mode); + src->rename_used++; + src->count++; dst = rename_dst[dst_index].two; - two = alloc_filespec(dst->path); - fill_filespec(two, dst->sha1, dst->mode); + dst->count++; - dp = diff_queue(NULL, one, two); + dp = diff_queue(NULL, src, dst); dp->renamed_pair = 1; if (!strcmp(src->path, dst->path)) dp->score = rename_src[src_index].score; else dp->score = score; - dp->source_stays = rename_src[src_index].src_path_left; rename_dst[dst_index].pair = dp; } @@ -247,19 +229,155 @@ static int score_compare(const void *a_, const void *b_) return b->score - a->score; } -static int compute_stays(struct diff_queue_struct *q, - struct diff_filespec *one) +struct file_similarity { + int src_dst, index; + struct diff_filespec *filespec; + struct file_similarity *next; +}; + +static int find_identical_files(struct file_similarity *src, + struct file_similarity *dst) { - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (strcmp(one->path, p->two->path)) - continue; - if (DIFF_PAIR_RENAME(p)) { - return 0; /* something else is renamed into this */ + int renames = 0; + + /* + * Walk over all the destinations ... + */ + do { + struct diff_filespec *one = dst->filespec; + struct file_similarity *p, *best; + int i = 100; + + /* + * .. to find the best source match + */ + best = NULL; + for (p = src; p; p = p->next) { + struct diff_filespec *two = p->filespec; + + /* False hash collission? */ + if (hashcmp(one->sha1, two->sha1)) + continue; + /* Non-regular files? If so, the modes must match! */ + if (!S_ISREG(one->mode) || !S_ISREG(two->mode)) { + if (one->mode != two->mode) + continue; + } + best = p; + if (basename_same(one, two)) + break; + + /* Too many identical alternatives? Pick one */ + if (!--i) + break; + } + if (best) { + record_rename_pair(dst->index, best->index, MAX_SCORE); + renames++; } + } while ((dst = dst->next) != NULL); + return renames; +} + +static void free_similarity_list(struct file_similarity *p) +{ + while (p) { + struct file_similarity *entry = p; + p = p->next; + free(entry); } - return 1; +} + +static int find_same_files(void *ptr) +{ + int ret; + struct file_similarity *p = ptr; + struct file_similarity *src = NULL, *dst = NULL; + + /* Split the hash list up into sources and destinations */ + do { + struct file_similarity *entry = p; + p = p->next; + if (entry->src_dst < 0) { + entry->next = src; + src = entry; + } else { + entry->next = dst; + dst = entry; + } + } while (p); + + /* + * If we have both sources *and* destinations, see if + * we can match them up + */ + ret = (src && dst) ? find_identical_files(src, dst) : 0; + + /* Free the hashes and return the number of renames found */ + free_similarity_list(src); + free_similarity_list(dst); + return ret; +} + +static unsigned int hash_filespec(struct diff_filespec *filespec) +{ + unsigned int hash; + if (!filespec->sha1_valid) { + if (diff_populate_filespec(filespec, 0)) + return 0; + hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1); + } + memcpy(&hash, filespec->sha1, sizeof(hash)); + return hash; +} + +static void insert_file_table(struct hash_table *table, int src_dst, int index, struct diff_filespec *filespec) +{ + void **pos; + unsigned int hash; + struct file_similarity *entry = xmalloc(sizeof(*entry)); + + entry->src_dst = src_dst; + entry->index = index; + entry->filespec = filespec; + entry->next = NULL; + + hash = hash_filespec(filespec); + pos = insert_hash(hash, entry, table); + + /* We already had an entry there? */ + if (pos) { + entry->next = *pos; + *pos = entry; + } +} + +/* + * Find exact renames first. + * + * The first round matches up the up-to-date entries, + * and then during the second round we try to match + * cache-dirty entries as well. + */ +static int find_exact_renames(void) +{ + int i; + struct hash_table file_table; + + init_hash(&file_table); + for (i = 0; i < rename_src_nr; i++) + insert_file_table(&file_table, -1, i, rename_src[i].one); + + for (i = 0; i < rename_dst_nr; i++) + insert_file_table(&file_table, 1, i, rename_dst[i].two); + + /* Find the renames */ + i = for_each_hash(&file_table, find_same_files); + + /* .. and free the hash data structure */ + free_hash(&file_table); + + return i; } void diffcore_rename(struct diff_options *options) @@ -270,12 +388,11 @@ void diffcore_rename(struct diff_options *options) struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; struct diff_score *mx; - int i, j, rename_count, contents_too; + int i, j, rename_count; int num_create, num_src, dst_cnt; if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; - rename_count = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -289,81 +406,66 @@ void diffcore_rename(struct diff_options *options) locate_rename_dst(p->two, 1); } else if (!DIFF_FILE_VALID(p->two)) { - /* If the source is a broken "delete", and + /* + * If the source is a broken "delete", and * they did not really want to get broken, * that means the source actually stays. + * So we increment the "rename_used" score + * by one, to indicate ourselves as a user + */ + if (p->broken_pair && !p->score) + p->one->rename_used++; + register_rename_src(p->one, p->score); + } + else if (detect_rename == DIFF_DETECT_COPY) { + /* + * Increment the "rename_used" score by + * one, to indicate ourselves as a user. */ - int stays = (p->broken_pair && !p->score); - register_rename_src(p->one, stays, p->score); + p->one->rename_used++; + register_rename_src(p->one, p->score); } - else if (detect_rename == DIFF_DETECT_COPY) - register_rename_src(p->one, 1, p->score); } if (rename_dst_nr == 0 || rename_src_nr == 0) goto cleanup; /* nothing to do */ /* + * We really want to cull the candidates list early + * with cheap tests in order to avoid doing deltas. + */ + rename_count = find_exact_renames(); + + /* Did we only want exact renames? */ + if (minimum_score == MAX_SCORE) + goto cleanup; + + /* + * Calculate how many renames are left (but all the source + * files still remain as options for rename/copies!) + */ + num_create = (rename_dst_nr - rename_count); + num_src = rename_src_nr; + + /* All done? */ + if (!num_create) + goto cleanup; + + /* * This basically does a test for the rename matrix not * growing larger than a "rename_limit" square matrix, ie: * - * rename_dst_nr * rename_src_nr > rename_limit * rename_limit + * num_create * num_src > rename_limit * rename_limit * * but handles the potential overflow case specially (and we * assume at least 32-bit integers) */ if (rename_limit <= 0 || rename_limit > 32767) rename_limit = 32767; - if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) + if (num_create > rename_limit && num_src > rename_limit) goto cleanup; - if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) + if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - /* We really want to cull the candidates list early - * with cheap tests in order to avoid doing deltas. - * The first round matches up the up-to-date entries, - * and then during the second round we try to match - * cache-dirty entries as well. - */ - for (contents_too = 0; contents_too < 2; contents_too++) { - for (i = 0; i < rename_dst_nr; i++) { - struct diff_filespec *two = rename_dst[i].two; - if (rename_dst[i].pair) - continue; /* dealt with an earlier round */ - for (j = 0; j < rename_src_nr; j++) { - int k; - struct diff_filespec *one = rename_src[j].one; - if (!is_exact_match(one, two, contents_too)) - continue; - - /* see if there is a basename match, too */ - for (k = j; k < rename_src_nr; k++) { - one = rename_src[k].one; - if (basename_same(one, two) && - is_exact_match(one, two, - contents_too)) { - j = k; - break; - } - } - - record_rename_pair(i, j, (int)MAX_SCORE); - rename_count++; - break; /* we are done with this entry */ - } - } - } - - /* Have we run out the created file pool? If so we can avoid - * doing the delta matrix altogether. - */ - if (rename_count == rename_dst_nr) - goto cleanup; - - if (minimum_score == MAX_SCORE) - goto cleanup; - - num_create = (rename_dst_nr - rename_count); - num_src = rename_src_nr; mx = xmalloc(sizeof(*mx) * num_create * num_src); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { int base = dst_cnt * num_src; @@ -452,16 +554,7 @@ void diffcore_rename(struct diff_options *options) pair_to_free = p; } else { - for (j = 0; j < rename_dst_nr; j++) { - if (!rename_dst[j].pair) - continue; - if (strcmp(rename_dst[j].pair-> - one->path, - p->one->path)) - continue; - break; - } - if (j < rename_dst_nr) + if (p->one->rename_used) /* this path remains */ pair_to_free = p; } @@ -487,27 +580,8 @@ void diffcore_rename(struct diff_options *options) *q = outq; diff_debug_queue("done collapsing", q); - /* We need to see which rename source really stays here; - * earlier we only checked if the path is left in the result, - * but even if a path remains in the result, if that is coming - * from copying something else on top of it, then the original - * source is lost and does not stay. - */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (DIFF_PAIR_RENAME(p) && p->source_stays) { - /* If one appears as the target of a rename-copy, - * then mark p->source_stays = 0; otherwise - * leave it as is. - */ - p->source_stays = compute_stays(q, p->one); - } - } - - for (i = 0; i < rename_dst_nr; i++) { - diff_free_filespec_data(rename_dst[i].two); - free(rename_dst[i].two); - } + for (i = 0; i < rename_dst_nr; i++) + free_filespec(rename_dst[i].two); free(rename_dst); rename_dst = NULL; diff --git a/diffcore.h b/diffcore.h index eb618b1ec0..cc96c20734 100644 --- a/diffcore.h +++ b/diffcore.h @@ -29,7 +29,9 @@ struct diff_filespec { void *cnt_data; const char *funcname_pattern_ident; unsigned long size; + int count; /* Reference count */ int xfrm_flags; /* for use by the xfrm */ + int rename_used; /* Count of rename users */ unsigned short mode; /* file mode */ unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; * if false, use the name and read from @@ -43,6 +45,7 @@ struct diff_filespec { }; extern struct diff_filespec *alloc_filespec(const char *); +extern void free_filespec(struct diff_filespec *); extern void fill_filespec(struct diff_filespec *, const unsigned char *, unsigned short); @@ -56,7 +59,6 @@ struct diff_filepair { struct diff_filespec *two; unsigned short int score; char status; /* M C R N D U (see Documentation/diff-format.txt) */ - unsigned source_stays : 1; /* all of R/C are copies */ unsigned broken_pair : 1; unsigned renamed_pair : 1; unsigned is_unmerged : 1; @@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre return retval; } +static int no_wildcard(const char *string) +{ + return string[strcspn(string, "*?[{")] == '\0'; +} + void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { struct exclude *x = xmalloc(sizeof (*x)); + x->to_exclude = 1; + if (*string == '!') { + x->to_exclude = 0; + string++; + } x->pattern = string; + x->patternlen = strlen(string); x->base = base; x->baselen = baselen; + x->flags = 0; + if (!strchr(string, '/')) + x->flags |= EXC_FLAG_NODIR; + if (no_wildcard(string)) + x->flags |= EXC_FLAG_NOWILDCARD; + if (*string == '*' && no_wildcard(string+1)) + x->flags |= EXC_FLAG_ENDSWITH; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); which->excludes = xrealloc(which->excludes, @@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int stk) * Return 1 for exclude, 0 for include and -1 for undecided. */ static int excluded_1(const char *pathname, - int pathlen, + int pathlen, const char *basename, struct exclude_list *el) { int i; @@ -218,19 +236,21 @@ static int excluded_1(const char *pathname, for (i = el->nr - 1; 0 <= i; i--) { struct exclude *x = el->excludes[i]; const char *exclude = x->pattern; - int to_exclude = 1; + int to_exclude = x->to_exclude; - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } - - if (!strchr(exclude, '/')) { + if (x->flags & EXC_FLAG_NODIR) { /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; + if (x->flags & EXC_FLAG_NOWILDCARD) { + if (!strcmp(exclude, basename)) + return to_exclude; + } else if (x->flags & EXC_FLAG_ENDSWITH) { + if (x->patternlen - 1 <= pathlen && + !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1)) + return to_exclude; + } else { + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } } else { /* match with FNM_PATHNAME: @@ -246,9 +266,14 @@ static int excluded_1(const char *pathname, strncmp(pathname, x->base, baselen)) continue; - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; + if (x->flags & EXC_FLAG_NOWILDCARD) { + if (!strcmp(exclude, pathname + baselen)) + return to_exclude; + } else { + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } } } } @@ -259,9 +284,11 @@ int excluded(struct dir_struct *dir, const char *pathname) { int pathlen = strlen(pathname); int st; + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) { + switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) { case 0: return 0; case 1: @@ -271,7 +298,8 @@ int excluded(struct dir_struct *dir, const char *pathname) return 0; } -static struct dir_entry *dir_entry_new(const char *pathname, int len) { +static struct dir_entry *dir_entry_new(const char *pathname, int len) +{ struct dir_entry *ent; ent = xmalloc(sizeof(*ent) + len + 1); @@ -712,6 +740,47 @@ int is_inside_dir(const char *dir) return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL; } +int remove_dir_recursively(struct strbuf *path, int only_empty) +{ + DIR *dir = opendir(path->buf); + struct dirent *e; + int ret = 0, original_len = path->len, len; + + if (!dir) + return -1; + if (path->buf[original_len - 1] != '/') + strbuf_addch(path, '/'); + + len = path->len; + while ((e = readdir(dir)) != NULL) { + struct stat st; + if ((e->d_name[0] == '.') && + ((e->d_name[1] == 0) || + ((e->d_name[1] == '.') && e->d_name[2] == 0))) + continue; /* "." and ".." */ + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); + if (lstat(path->buf, &st)) + ; /* fall thru */ + else if (S_ISDIR(st.st_mode)) { + if (!remove_dir_recursively(path, only_empty)) + continue; /* happy */ + } else if (!only_empty && !unlink(path->buf)) + continue; /* happy, too */ + + /* path too long, stat fails, or non-directory still exists */ + ret = -1; + break; + } + closedir(dir); + + strbuf_setlen(path, original_len); + if (!ret) + ret = rmdir(path->buf); + return ret; +} + void setup_standard_excludes(struct dir_struct *dir) { const char *path; @@ -17,13 +17,20 @@ struct dir_entry { char name[FLEX_ARRAY]; /* more */ }; +#define EXC_FLAG_NODIR 1 +#define EXC_FLAG_NOWILDCARD 2 +#define EXC_FLAG_ENDSWITH 4 + struct exclude_list { int nr; int alloc; struct exclude { const char *pattern; + int patternlen; const char *base; int baselen; + int to_exclude; + int flags; } **excludes; }; @@ -65,5 +72,6 @@ extern char *get_relative_cwd(char *buffer, int size, const char *dir); extern int is_inside_dir(const char *dir); extern void setup_standard_excludes(struct dir_struct *dir); +extern int remove_dir_recursively(struct strbuf *path, int only_empty); #endif @@ -104,7 +104,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout long wrote; switch (ntohl(ce->ce_mode) & S_IFMT) { - char *buf, *new; + char *new; + struct strbuf buf; unsigned long size; case S_IFREG: @@ -116,10 +117,12 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout /* * Convert from git internal format to working tree format */ - buf = convert_to_working_tree(ce->name, new, &size); - if (buf) { + strbuf_init(&buf, 0); + if (convert_to_working_tree(ce->name, new, size, &buf)) { + size_t newsize = 0; free(new); - new = buf; + new = strbuf_detach(&buf, &newsize); + size = newsize; } if (to_tempfile) { diff --git a/exec_cmd.c b/exec_cmd.c index 9b74ed2f42..2d0a758512 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,11 +5,11 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path; +static const char *argv_exec_path; -void git_set_exec_path(const char *exec_path) +void git_set_argv_exec_path(const char *exec_path) { - current_exec_path = exec_path; + argv_exec_path = exec_path; } @@ -18,8 +18,8 @@ const char *git_exec_path(void) { const char *env; - if (current_exec_path) - return current_exec_path; + if (argv_exec_path) + return argv_exec_path; env = getenv(EXEC_PATH_ENVIRONMENT); if (env && *env) { @@ -29,85 +29,69 @@ const char *git_exec_path(void) return builtin_exec_path; } +static void add_path(struct strbuf *out, const char *path) +{ + if (path && *path) { + if (is_absolute_path(path)) + strbuf_addstr(out, path); + else + strbuf_addstr(out, make_absolute_path(path)); + + strbuf_addch(out, ':'); + } +} + +void setup_path(const char *cmd_path) +{ + const char *old_path = getenv("PATH"); + struct strbuf new_path; + + strbuf_init(&new_path, 0); + + add_path(&new_path, argv_exec_path); + add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); + add_path(&new_path, builtin_exec_path); + add_path(&new_path, cmd_path); + + if (old_path) + strbuf_addstr(&new_path, old_path); + else + strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + + setenv("PATH", new_path.buf, 1); + + strbuf_release(&new_path); +} int execv_git_cmd(const char **argv) { - char git_command[PATH_MAX + 1]; - int i; - const char *paths[] = { current_exec_path, - getenv(EXEC_PATH_ENVIRONMENT), - builtin_exec_path }; - - for (i = 0; i < ARRAY_SIZE(paths); ++i) { - size_t len; - int rc; - const char *exec_dir = paths[i]; - const char *tmp; - - if (!exec_dir || !*exec_dir) continue; - - if (*exec_dir != '/') { - if (!getcwd(git_command, sizeof(git_command))) { - fprintf(stderr, "git: cannot determine " - "current directory: %s\n", - strerror(errno)); - break; - } - len = strlen(git_command); - - /* Trivial cleanup */ - while (!prefixcmp(exec_dir, "./")) { - exec_dir += 2; - while (*exec_dir == '/') - exec_dir++; - } - - rc = snprintf(git_command + len, - sizeof(git_command) - len, "/%s", - exec_dir); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - } else { - if (strlen(exec_dir) + 1 > sizeof(git_command)) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - strcpy(git_command, exec_dir); - } - - len = strlen(git_command); - rc = snprintf(git_command + len, sizeof(git_command) - len, - "/git-%s", argv[0]); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, - "git: command name given is too long.\n"); - break; - } + struct strbuf cmd; + const char *tmp; - /* argv[0] must be the git command, but the argv array - * belongs to the caller, and my be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ + strbuf_init(&cmd, 0); + strbuf_addf(&cmd, "git-%s", argv[0]); - tmp = argv[0]; - argv[0] = git_command; + /* + * argv[0] must be the git command, but the argv array + * belongs to the caller, and may be reused in + * subsequent loop iterations. Save argv[0] and + * restore it on error. + */ + tmp = argv[0]; + argv[0] = cmd.buf; - trace_argv_printf(argv, -1, "trace: exec:"); + trace_argv_printf(argv, -1, "trace: exec:"); - /* execve() can only ever return if it fails */ - execve(git_command, (char **)argv, environ); + /* execvp() can only ever return if it fails */ + execvp(cmd.buf, (char **)argv); - trace_printf("trace: exec failed: %s\n", strerror(errno)); + trace_printf("trace: exec failed: %s\n", strerror(errno)); - argv[0] = tmp; - } - return -1; + argv[0] = tmp; + strbuf_release(&cmd); + + return -1; } diff --git a/exec_cmd.h b/exec_cmd.h index 849a8395a0..a892355c82 100644 --- a/exec_cmd.h +++ b/exec_cmd.h @@ -1,8 +1,9 @@ #ifndef GIT_EXEC_CMD_H #define GIT_EXEC_CMD_H -extern void git_set_exec_path(const char *exec_path); +extern void git_set_argv_exec_path(const char *exec_path); extern const char* git_exec_path(void); +extern void setup_path(const char *); extern int execv_git_cmd(const char **argv); /* NULL terminated */ extern int execl_git_cmd(const char *cmd, ...); diff --git a/fast-import.c b/fast-import.c index 5e83296bf4..98c2bd5359 100644 --- a/fast-import.c +++ b/fast-import.c @@ -149,7 +149,6 @@ Format of STDIN stream: #include "pack.h" #include "refs.h" #include "csum-file.h" -#include "strbuf.h" #include "quote.h" #define PACK_ID_BITS 16 @@ -186,11 +185,10 @@ struct mark_set struct last_object { - void *data; - unsigned long len; + struct strbuf data; uint32_t offset; unsigned int depth; - unsigned no_free:1; + unsigned no_swap : 1; }; struct mem_pool @@ -254,12 +252,6 @@ struct tag unsigned char sha1[20]; }; -struct dbuf -{ - void *buffer; - size_t capacity; -}; - struct hash_list { struct hash_list *next; @@ -320,15 +312,15 @@ static struct mark_set *marks; static const char* mark_file; /* Our last blob */ -static struct last_object last_blob; +static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 }; /* Tree management */ static unsigned int tree_entry_alloc = 1000; static void *avail_tree_entry; static unsigned int avail_tree_table_sz = 100; static struct avail_tree_content **avail_tree_table; -static struct dbuf old_tree; -static struct dbuf new_tree; +static struct strbuf old_tree = STRBUF_INIT; +static struct strbuf new_tree = STRBUF_INIT; /* Branch data */ static unsigned long max_active_branches = 5; @@ -343,14 +335,14 @@ static struct tag *last_tag; /* Input stream parsing */ static whenspec_type whenspec = WHENSPEC_RAW; -static struct strbuf command_buf; +static struct strbuf command_buf = STRBUF_INIT; static int unread_command_buf; static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL}; static struct recent_command *cmd_tail = &cmd_hist; static struct recent_command *rc_free; static unsigned int cmd_save = 100; static uintmax_t next_mark; -static struct dbuf new_data; +static struct strbuf new_data = STRBUF_INIT; static void write_branch_report(FILE *rpt, struct branch *b) { @@ -570,17 +562,6 @@ static char *pool_strdup(const char *s) return r; } -static void size_dbuf(struct dbuf *b, size_t maxlen) -{ - if (b->buffer) { - if (b->capacity >= maxlen) - return; - free(b->buffer); - } - b->capacity = ((maxlen / 1024) + 1) * 1024; - b->buffer = xmalloc(b->capacity); -} - static void insert_mark(uintmax_t idnum, struct object_entry *oe) { struct mark_set *s = marks; @@ -971,9 +952,7 @@ static void end_packfile(void) free(old_p); /* We can't carry a delta across packfiles. */ - free(last_blob.data); - last_blob.data = NULL; - last_blob.len = 0; + strbuf_release(&last_blob.data); last_blob.offset = 0; last_blob.depth = 0; } @@ -1009,8 +988,7 @@ static size_t encode_header( static int store_object( enum object_type type, - void *dat, - size_t datlen, + struct strbuf *dat, struct last_object *last, unsigned char *sha1out, uintmax_t mark) @@ -1024,10 +1002,10 @@ static int store_object( z_stream s; hdrlen = sprintf((char*)hdr,"%s %lu", typename(type), - (unsigned long)datlen) + 1; + (unsigned long)dat->len) + 1; SHA1_Init(&c); SHA1_Update(&c, hdr, hdrlen); - SHA1_Update(&c, dat, datlen); + SHA1_Update(&c, dat->buf, dat->len); SHA1_Final(sha1, &c); if (sha1out) hashcpy(sha1out, sha1); @@ -1046,11 +1024,11 @@ static int store_object( return 1; } - if (last && last->data && last->depth < max_depth) { - delta = diff_delta(last->data, last->len, - dat, datlen, + if (last && last->data.buf && last->depth < max_depth) { + delta = diff_delta(last->data.buf, last->data.len, + dat->buf, dat->len, &deltalen, 0); - if (delta && deltalen >= datlen) { + if (delta && deltalen >= dat->len) { free(delta); delta = NULL; } @@ -1063,8 +1041,8 @@ static int store_object( s.next_in = delta; s.avail_in = deltalen; } else { - s.next_in = dat; - s.avail_in = datlen; + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; } s.avail_out = deflateBound(&s, s.avail_in); s.next_out = out = xmalloc(s.avail_out); @@ -1087,8 +1065,8 @@ static int store_object( memset(&s, 0, sizeof(s)); deflateInit(&s, zlib_compression_level); - s.next_in = dat; - s.avail_in = datlen; + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; s.avail_out = deflateBound(&s, s.avail_in); s.next_out = out = xrealloc(out, s.avail_out); while (deflate(&s, Z_FINISH) == Z_OK) @@ -1121,7 +1099,7 @@ static int store_object( pack_size += sizeof(hdr) - pos; } else { e->depth = 0; - hdrlen = encode_header(type, datlen, hdr); + hdrlen = encode_header(type, dat->len, hdr); write_or_die(pack_data->pack_fd, hdr, hdrlen); pack_size += hdrlen; } @@ -1132,12 +1110,13 @@ static int store_object( free(out); free(delta); if (last) { - if (!last->no_free) - free(last->data); - last->data = dat; + if (last->no_swap) { + last->data = *dat; + } else { + strbuf_swap(&last->data, dat); + } last->offset = e->offset; last->depth = e->depth; - last->len = datlen; } return 0; } @@ -1233,14 +1212,10 @@ static int tecmp1 (const void *_a, const void *_b) b->name->str_dat, b->name->str_len, b->versions[1].mode); } -static void mktree(struct tree_content *t, - int v, - unsigned long *szp, - struct dbuf *b) +static void mktree(struct tree_content *t, int v, struct strbuf *b) { size_t maxlen = 0; unsigned int i; - char *c; if (!v) qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp0); @@ -1252,28 +1227,23 @@ static void mktree(struct tree_content *t, maxlen += t->entries[i]->name->str_len + 34; } - size_dbuf(b, maxlen); - c = b->buffer; + strbuf_reset(b); + strbuf_grow(b, maxlen); for (i = 0; i < t->entry_count; i++) { struct tree_entry *e = t->entries[i]; if (!e->versions[v].mode) continue; - c += sprintf(c, "%o", (unsigned int)e->versions[v].mode); - *c++ = ' '; - strcpy(c, e->name->str_dat); - c += e->name->str_len + 1; - hashcpy((unsigned char*)c, e->versions[v].sha1); - c += 20; + strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode, + e->name->str_dat, '\0'); + strbuf_add(b, e->versions[v].sha1, 20); } - *szp = c - (char*)b->buffer; } static void store_tree(struct tree_entry *root) { struct tree_content *t = root->tree; unsigned int i, j, del; - unsigned long new_len; - struct last_object lo; + struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 }; struct object_entry *le; if (!is_null_sha1(root->versions[1].sha1)) @@ -1285,23 +1255,15 @@ static void store_tree(struct tree_entry *root) } le = find_object(root->versions[0].sha1); - if (!S_ISDIR(root->versions[0].mode) - || !le - || le->pack_id != pack_id) { - lo.data = NULL; - lo.depth = 0; - lo.no_free = 0; - } else { - mktree(t, 0, &lo.len, &old_tree); - lo.data = old_tree.buffer; + if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { + mktree(t, 0, &old_tree); + lo.data = old_tree; lo.offset = le->offset; lo.depth = t->delta_depth; - lo.no_free = 1; } - mktree(t, 1, &new_len, &new_tree); - store_object(OBJ_TREE, new_tree.buffer, new_len, - &lo, root->versions[1].sha1, 0); + mktree(t, 1, &new_tree); + store_object(OBJ_TREE, &new_tree, &lo, root->versions[1].sha1, 0); t->delta_depth = lo.depth; for (i = 0, j = 0, del = 0; i < t->entry_count; i++) { @@ -1588,20 +1550,25 @@ static void dump_marks(void) mark_file, strerror(errno)); } -static void read_next_command(void) +static int read_next_command(void) { + static int stdin_eof = 0; + + if (stdin_eof) { + unread_command_buf = 0; + return EOF; + } + do { if (unread_command_buf) { unread_command_buf = 0; - if (command_buf.eof) - return; } else { struct recent_command *rc; - command_buf.buf = NULL; - read_line(&command_buf, stdin, '\n'); - if (command_buf.eof) - return; + strbuf_detach(&command_buf, NULL); + stdin_eof = strbuf_getline(&command_buf, stdin, '\n'); + if (stdin_eof) + return EOF; rc = rc_free; if (rc) @@ -1620,6 +1587,8 @@ static void read_next_command(void) cmd_tail = rc; } } while (command_buf.buf[0] == '#'); + + return 0; } static void skip_optional_lf(void) @@ -1639,42 +1608,36 @@ static void cmd_mark(void) next_mark = 0; } -static void *cmd_data (size_t *size) +static void cmd_data(struct strbuf *sb) { - size_t length; - char *buffer; + strbuf_reset(sb); if (prefixcmp(command_buf.buf, "data ")) die("Expected 'data n' command, found: %s", command_buf.buf); if (!prefixcmp(command_buf.buf + 5, "<<")) { char *term = xstrdup(command_buf.buf + 5 + 2); - size_t sz = 8192, term_len = command_buf.len - 5 - 2; - length = 0; - buffer = xmalloc(sz); - command_buf.buf = NULL; + size_t term_len = command_buf.len - 5 - 2; + + strbuf_detach(&command_buf, NULL); for (;;) { - read_line(&command_buf, stdin, '\n'); - if (command_buf.eof) + if (strbuf_getline(&command_buf, stdin, '\n') == EOF) die("EOF in data (terminator '%s' not found)", term); if (term_len == command_buf.len && !strcmp(term, command_buf.buf)) break; - ALLOC_GROW(buffer, length + command_buf.len, sz); - memcpy(buffer + length, - command_buf.buf, - command_buf.len - 1); - length += command_buf.len - 1; - buffer[length++] = '\n'; + strbuf_addbuf(sb, &command_buf); + strbuf_addch(sb, '\n'); } free(term); } else { - size_t n = 0; + size_t n = 0, length; + length = strtoul(command_buf.buf + 5, NULL, 10); - buffer = xmalloc(length); + while (n < length) { - size_t s = fread(buffer + n, 1, length - n, stdin); + size_t s = strbuf_fread(sb, length - n, stdin); if (!s && feof(stdin)) die("EOF in data (%lu bytes remaining)", (unsigned long)(length - n)); @@ -1683,8 +1646,6 @@ static void *cmd_data (size_t *size) } skip_optional_lf(); - *size = length; - return buffer; } static int validate_raw_date(const char *src, char *result, int maxlen) @@ -1747,15 +1708,12 @@ static char *parse_ident(const char *buf) static void cmd_new_blob(void) { - size_t l; - void *d; + static struct strbuf buf = STRBUF_INIT; read_next_command(); cmd_mark(); - d = cmd_data(&l); - - if (store_object(OBJ_BLOB, d, l, &last_blob, NULL, next_mark)) - free(d); + cmd_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark); } static void unload_one_branch(void) @@ -1805,7 +1763,7 @@ static void load_branch(struct branch *b) static void file_change_m(struct branch *b) { const char *p = command_buf.buf + 2; - char *p_uq; + static struct strbuf uq = STRBUF_INIT; const char *endp; struct object_entry *oe = oe; unsigned char sha1[20]; @@ -1843,22 +1801,23 @@ static void file_change_m(struct branch *b) if (*p++ != ' ') die("Missing space after SHA1: %s", command_buf.buf); - p_uq = unquote_c_style(p, &endp); - if (p_uq) { + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { if (*endp) die("Garbage after path in: %s", command_buf.buf); - p = p_uq; + p = uq.buf; } if (inline_data) { - size_t l; - void *d; - if (!p_uq) - p = p_uq = xstrdup(p); + static struct strbuf buf = STRBUF_INIT; + + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } read_next_command(); - d = cmd_data(&l); - if (store_object(OBJ_BLOB, d, l, &last_blob, sha1, 0)) - free(d); + cmd_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0); } else if (oe) { if (oe->type != OBJ_BLOB) die("Not a blob (actually a %s): %s", @@ -1873,58 +1832,54 @@ static void file_change_m(struct branch *b) } tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL); - free(p_uq); } static void file_change_d(struct branch *b) { const char *p = command_buf.buf + 2; - char *p_uq; + static struct strbuf uq = STRBUF_INIT; const char *endp; - p_uq = unquote_c_style(p, &endp); - if (p_uq) { + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { if (*endp) die("Garbage after path in: %s", command_buf.buf); - p = p_uq; + p = uq.buf; } tree_content_remove(&b->branch_tree, p, NULL); - free(p_uq); } static void file_change_cr(struct branch *b, int rename) { const char *s, *d; - char *s_uq, *d_uq; + static struct strbuf s_uq = STRBUF_INIT; + static struct strbuf d_uq = STRBUF_INIT; const char *endp; struct tree_entry leaf; s = command_buf.buf + 2; - s_uq = unquote_c_style(s, &endp); - if (s_uq) { + strbuf_reset(&s_uq); + if (!unquote_c_style(&s_uq, s, &endp)) { if (*endp != ' ') die("Missing space after source: %s", command_buf.buf); - } - else { + } else { endp = strchr(s, ' '); if (!endp) die("Missing space after source: %s", command_buf.buf); - s_uq = xmalloc(endp - s + 1); - memcpy(s_uq, s, endp - s); - s_uq[endp - s] = 0; + strbuf_add(&s_uq, s, endp - s); } - s = s_uq; + s = s_uq.buf; endp++; if (!*endp) die("Missing dest: %s", command_buf.buf); d = endp; - d_uq = unquote_c_style(d, &endp); - if (d_uq) { + strbuf_reset(&d_uq); + if (!unquote_c_style(&d_uq, d, &endp)) { if (*endp) die("Garbage after dest in: %s", command_buf.buf); - d = d_uq; + d = d_uq.buf; } memset(&leaf, 0, sizeof(leaf)); @@ -1938,9 +1893,6 @@ static void file_change_cr(struct branch *b, int rename) leaf.versions[1].sha1, leaf.versions[1].mode, leaf.tree); - - free(s_uq); - free(d_uq); } static void file_change_deleteall(struct branch *b) @@ -2065,9 +2017,8 @@ static struct hash_list *cmd_merge(unsigned int *count) static void cmd_new_commit(void) { + static struct strbuf msg = STRBUF_INIT; struct branch *b; - void *msg; - size_t msglen; char *sp; char *author = NULL; char *committer = NULL; @@ -2092,7 +2043,7 @@ static void cmd_new_commit(void) } if (!committer) die("Expected committer but didn't get one"); - msg = cmd_data(&msglen); + cmd_data(&msg); read_next_command(); cmd_from(b); merge_list = cmd_merge(&merge_count); @@ -2104,7 +2055,7 @@ static void cmd_new_commit(void) } /* file_change* */ - while (!command_buf.eof && command_buf.len > 1) { + while (command_buf.len > 0) { if (!prefixcmp(command_buf.buf, "M ")) file_change_m(b); else if (!prefixcmp(command_buf.buf, "D ")) @@ -2119,53 +2070,47 @@ static void cmd_new_commit(void) unread_command_buf = 1; break; } - read_next_command(); + if (read_next_command() == EOF) + break; } /* build the tree and the commit */ store_tree(&b->branch_tree); hashcpy(b->branch_tree.versions[0].sha1, b->branch_tree.versions[1].sha1); - size_dbuf(&new_data, 114 + msglen - + merge_count * 49 - + (author - ? strlen(author) + strlen(committer) - : 2 * strlen(committer))); - sp = new_data.buffer; - sp += sprintf(sp, "tree %s\n", + + strbuf_reset(&new_data); + strbuf_addf(&new_data, "tree %s\n", sha1_to_hex(b->branch_tree.versions[1].sha1)); if (!is_null_sha1(b->sha1)) - sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1)); + strbuf_addf(&new_data, "parent %s\n", sha1_to_hex(b->sha1)); while (merge_list) { struct hash_list *next = merge_list->next; - sp += sprintf(sp, "parent %s\n", sha1_to_hex(merge_list->sha1)); + strbuf_addf(&new_data, "parent %s\n", sha1_to_hex(merge_list->sha1)); free(merge_list); merge_list = next; } - sp += sprintf(sp, "author %s\n", author ? author : committer); - sp += sprintf(sp, "committer %s\n", committer); - *sp++ = '\n'; - memcpy(sp, msg, msglen); - sp += msglen; + strbuf_addf(&new_data, + "author %s\n" + "committer %s\n" + "\n", + author ? author : committer, committer); + strbuf_addbuf(&new_data, &msg); free(author); free(committer); - free(msg); - if (!store_object(OBJ_COMMIT, - new_data.buffer, sp - (char*)new_data.buffer, - NULL, b->sha1, next_mark)) + if (!store_object(OBJ_COMMIT, &new_data, NULL, b->sha1, next_mark)) b->pack_id = pack_id; b->last_commit = object_count_by_type[OBJ_COMMIT]; } static void cmd_new_tag(void) { + static struct strbuf msg = STRBUF_INIT; char *sp; const char *from; char *tagger; struct branch *s; - void *msg; - size_t msglen; struct tag *t; uintmax_t from_mark = 0; unsigned char sha1[20]; @@ -2216,24 +2161,21 @@ static void cmd_new_tag(void) /* tag payload/message */ read_next_command(); - msg = cmd_data(&msglen); + cmd_data(&msg); /* build the tag object */ - size_dbuf(&new_data, 67+strlen(t->name)+strlen(tagger)+msglen); - sp = new_data.buffer; - sp += sprintf(sp, "object %s\n", sha1_to_hex(sha1)); - sp += sprintf(sp, "type %s\n", commit_type); - sp += sprintf(sp, "tag %s\n", t->name); - sp += sprintf(sp, "tagger %s\n", tagger); - *sp++ = '\n'; - memcpy(sp, msg, msglen); - sp += msglen; + strbuf_reset(&new_data); + strbuf_addf(&new_data, + "object %s\n" + "type %s\n" + "tag %s\n" + "tagger %s\n" + "\n", + sha1_to_hex(sha1), commit_type, t->name, tagger); + strbuf_addbuf(&new_data, &msg); free(tagger); - free(msg); - if (store_object(OBJ_TAG, new_data.buffer, - sp - (char*)new_data.buffer, - NULL, t->sha1, 0)) + if (store_object(OBJ_TAG, &new_data, NULL, t->sha1, 0)) t->pack_id = MAX_PACK_ID; else t->pack_id = pack_id; @@ -2259,7 +2201,7 @@ static void cmd_reset_branch(void) else b = new_branch(sp); read_next_command(); - if (!cmd_from(b) && command_buf.len > 1) + if (!cmd_from(b) && command_buf.len > 0) unread_command_buf = 1; } @@ -2276,7 +2218,7 @@ static void cmd_checkpoint(void) static void cmd_progress(void) { - fwrite(command_buf.buf, 1, command_buf.len - 1, stdout); + fwrite(command_buf.buf, 1, command_buf.len, stdout); fputc('\n', stdout); fflush(stdout); skip_optional_lf(); @@ -2326,7 +2268,7 @@ int main(int argc, const char **argv) git_config(git_default_config); alloc_objects(object_entry_alloc); - strbuf_init(&command_buf); + strbuf_init(&command_buf, 0); atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); @@ -2387,11 +2329,8 @@ int main(int argc, const char **argv) prepare_packed_git(); start_packfile(); set_die_routine(die_nicely); - for (;;) { - read_next_command(); - if (command_buf.eof) - break; - else if (!strcmp("blob", command_buf.buf)) + while (read_next_command() != EOF) { + if (!strcmp("blob", command_buf.buf)) cmd_new_blob(); else if (!prefixcmp(command_buf.buf, "commit ")) cmd_new_commit(); diff --git a/fetch-pack.h b/fetch-pack.h new file mode 100644 index 0000000000..a7888ea302 --- /dev/null +++ b/fetch-pack.h @@ -0,0 +1,24 @@ +#ifndef FETCH_PACK_H +#define FETCH_PACK_H + +struct fetch_pack_args +{ + const char *uploadpack; + int unpacklimit; + int depth; + unsigned quiet:1, + keep_pack:1, + lock_pack:1, + use_thin_pack:1, + fetch_all:1, + verbose:1, + no_progress:1; +}; + +struct ref *fetch_pack(struct fetch_pack_args *args, + const char *dest, + int nr_heads, + char **heads, + char **pack_lockfile); + +#endif diff --git a/fetch.h b/fetch.h deleted file mode 100644 index be48c6f190..0000000000 --- a/fetch.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef PULL_H -#define PULL_H - -/* - * Fetch object given SHA1 from the remote, and store it locally under - * GIT_OBJECT_DIRECTORY. Return 0 on success, -1 on failure. To be - * provided by the particular implementation. - */ -extern int fetch(unsigned char *sha1); - -/* - * Fetch the specified object and store it locally; fetch() will be - * called later to determine success. To be provided by the particular - * implementation. - */ -extern void prefetch(unsigned char *sha1); - -/* - * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store - * the 20-byte SHA1 in sha1. Return 0 on success, -1 on failure. To - * be provided by the particular implementation. - */ -extern int fetch_ref(char *ref, unsigned char *sha1); - -/* Set to fetch the target tree. */ -extern int get_tree; - -/* Set to fetch the commit history. */ -extern int get_history; - -/* Set to fetch the trees in the commit history. */ -extern int get_all; - -/* Set to be verbose */ -extern int get_verbosely; - -/* Set to check on all reachable objects. */ -extern int get_recover; - -/* Report what we got under get_verbosely */ -extern void pull_say(const char *, const char *); - -/* Load pull targets from stdin */ -extern int pull_targets_stdin(char ***target, const char ***write_ref); - -/* Free up loaded targets */ -extern void pull_targets_free(int targets, char **target, const char **write_ref); - -/* If write_ref is set, the ref filename to write the target value to. */ -/* If write_ref_log_details is set, additional text will appear in the ref log. */ -extern int pull(int targets, char **target, const char **write_ref, - const char *write_ref_log_details); - -#endif /* PULL_H */ diff --git a/git-add--interactive.perl b/git-add--interactive.perl index ac598f88e6..fb1e92a766 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -37,10 +37,7 @@ sub list_untracked { chomp $_; $_; } - run_cmd_pipe(qw(git ls-files --others - --exclude-per-directory=.gitignore), - "--exclude-from=$GIT_DIR/info/exclude", - '--', @_); + run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @_); } my $status_fmt = '%12s %12s %s'; @@ -567,10 +564,12 @@ sub patch_update_cmd { IMMEDIATE => 1, HEADER => $status_head, }, @mods); - return if (!$it); + patch_update_file($it->{VALUE}) if ($it); +} +sub patch_update_file { my ($ix, $num); - my $path = $it->{VALUE}; + my $path = shift; my ($head, @hunk) = parse_diff($path); for (@{$head->{TEXT}}) { print; @@ -2,11 +2,26 @@ # # Copyright (c) 2005, 2006 Junio C Hamano -USAGE='[--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8] - [--3way] [--interactive] [--binary] - [--whitespace=<option>] [-C<n>] [-p<n>] - <mbox>|<Maildir>... - or, when resuming [--skip | --resolved]' +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-am [options] <mbox>|<Maildir>... +git-am [options] --resolved +git-am [options] --skip +-- +d,dotest= use <dir> and not .dotest +i,interactive run interactively +b,binary pass --allo-binary-replacement to git-apply +3,3way allow fall back on 3way merging if needed +s,signoff add a Signed-off-by line to the commit message +u,utf8 recode into utf8 (default) +k,keep pass -k flagg to git-mailinfo +whitespace= pass it through git-apply +C= pass it through git-apply +p= pass it through git-apply +resolvemsg= override error message when patch failure occurs +r,resolved to be used after a patch failure +skip skip the current patch" + . git-sh-setup set_reflog_action am require_work_tree @@ -110,49 +125,38 @@ git_apply_opt= while test $# != 0 do case "$1" in - -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*) - dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;; - -d|--d|--do|--dot|--dote|--dotes|--dotest) - case "$#" in 1) usage ;; esac; shift - dotest="$1"; shift;; - - -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\ - --interacti|--interactiv|--interactive) - interactive=t; shift ;; - - -b|--b|--bi|--bin|--bina|--binar|--binary) - binary=t; shift ;; - - -3|--3|--3w|--3wa|--3way) - threeway=t; shift ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - sign=t; shift ;; - -u|--u|--ut|--utf|--utf8) - utf8=t; shift ;; # this is now default - --no-u|--no-ut|--no-utf|--no-utf8) - utf8=; shift ;; - -k|--k|--ke|--kee|--keep) - keep=t; shift ;; - - -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved) - resolved=t; shift ;; - - --sk|--ski|--skip) - skip=t; shift ;; - - --whitespace=*|-C*|-p*) - git_apply_opt="$git_apply_opt $1"; shift ;; - - --resolvemsg=*) - resolvemsg=${1#--resolvemsg=}; shift ;; - + -i|--interactive) + interactive=t ;; + -b|--binary) + binary=t ;; + -3|--3way) + threeway=t ;; + -s|--signoff) + sign=t ;; + -u|--utf8) + utf8=t ;; # this is now default + --no-utf8) + utf8= ;; + -k|--keep) + keep=t ;; + -r|--resolved) + resolved=t ;; + --skip) + skip=t ;; + -d|--dotest) + shift; dotest=$1;; + --resolvemsg) + shift; resolvemsg=$1 ;; + --whitespace) + git_apply_opt="$git_apply_opt $1=$2"; shift ;; + -C|-p) + git_apply_opt="$git_apply_opt $1$2"; shift ;; --) - shift; break ;; - -*) - usage ;; + shift; break ;; *) - break ;; + usage ;; esac + shift done # If the dotest directory exists, but we have finished applying all the @@ -394,9 +398,7 @@ do stop_here $this fi - echo printf 'Applying %s\n' "$SUBJECT" - echo case "$resolved" in '') @@ -452,10 +454,8 @@ do fi tree=$(git write-tree) && - echo Wrote tree $tree && parent=$(git rev-parse --verify HEAD) && commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") && - echo Committed: $commit && git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent || stop_here $this @@ -464,6 +464,8 @@ do "$GIT_DIR"/hooks/post-applypatch fi + git gc --auto + go_next done diff --git a/git-bisect.sh b/git-bisect.sh index 388887a556..7a6521ec3c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,12 +1,14 @@ #!/bin/sh -USAGE='[start|bad|good|next|reset|visualize|replay|log|run]' +USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...] reset bisect state and start bisection. git bisect bad [<rev>] mark <rev> a known-bad revision. git bisect good [<rev>...] mark <rev>... known-good revisions. +git bisect skip [<rev>...] + mark <rev>... untestable revisions. git bisect next find next bisection to test and check it out. git bisect reset [<branch>] @@ -20,6 +22,7 @@ git bisect log git bisect run <cmd>... use <cmd>... to automatically bisect.' +OPTIONS_SPEC= . git-sh-setup require_work_tree @@ -34,7 +37,7 @@ sq() { } bisect_autostart() { - test -d "$GIT_DIR/refs/bisect" || { + test -f "$GIT_DIR/BISECT_NAMES" || { echo >&2 'You need to start by "git bisect start"' if test -t 0 then @@ -64,12 +67,12 @@ bisect_start() { branch=`cat "$GIT_DIR/head-name"` else branch=master - fi + fi git checkout $branch || exit ;; refs/heads/*) [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree" - echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name" + echo "${head#refs/heads/}" >"$GIT_DIR/head-name" ;; *) die "Bad HEAD - strange symbolic ref" @@ -80,7 +83,6 @@ bisect_start() { # Get rid of any old bisect state # bisect_clean_state - mkdir "$GIT_DIR/refs/bisect" # # Check for one bad and then some good revisions. @@ -95,75 +97,74 @@ bisect_start() { arg="$1" case "$arg" in --) - shift + shift break ;; *) - rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { + rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { test $has_double_dash -eq 1 && die "'$arg' does not appear to be a valid revision" break } - if [ $bad_seen -eq 0 ]; then - bad_seen=1 - bisect_write_bad "$rev" - else - bisect_write_good "$rev" - fi - shift + case $bad_seen in + 0) state='bad' ; bad_seen=1 ;; + *) state='good' ;; + esac + bisect_write "$state" "$rev" 'nolog' + shift ;; esac - done + done sq "$@" >"$GIT_DIR/BISECT_NAMES" echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } -bisect_bad() { - bisect_autostart - case "$#" in - 0) - rev=$(git rev-parse --verify HEAD) ;; - 1) - rev=$(git rev-parse --verify "$1^{commit}") ;; - *) - usage ;; - esac || exit - bisect_write_bad "$rev" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - bisect_auto_next -} - -bisect_write_bad() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" +bisect_write() { + state="$1" + rev="$2" + nolog="$3" + case "$state" in + bad) tag="$state" ;; + good|skip) tag="$state"-"$rev" ;; + *) die "Bad bisect_write argument: $state" ;; + esac + git update-ref "refs/bisect/$tag" "$rev" + echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } -bisect_good() { +bisect_state() { bisect_autostart - case "$#" in - 0) revs=$(git rev-parse --verify HEAD) || exit ;; - *) revs=$(git rev-parse --revs-only --no-flags "$@") && - test '' != "$revs" || die "Bad rev input: $@" ;; + state=$1 + case "$#,$state" in + 0,*) + die "Please call 'bisect_state' with at least one argument." ;; + 1,bad|1,good|1,skip) + rev=$(git rev-parse --verify HEAD) || + die "Bad rev input: HEAD" + bisect_write "$state" "$rev" ;; + 2,bad) + rev=$(git rev-parse --verify "$2^{commit}") || + die "Bad rev input: $2" + bisect_write "$state" "$rev" ;; + *,good|*,skip) + shift + revs=$(git rev-parse --revs-only --no-flags "$@") && + test '' != "$revs" || die "Bad rev input: $@" + for rev in $revs + do + rev=$(git rev-parse --verify "$rev^{commit}") || + die "Bad rev commit: $rev^{commit}" + bisect_write "$state" "$rev" + done ;; + *) + usage ;; esac - for rev in $revs - do - rev=$(git rev-parse --verify "$rev^{commit}") || exit - bisect_write_good "$rev" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - - done bisect_auto_next } -bisect_write_good() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" -} - bisect_next_check() { missing_good= missing_bad= git show-ref -q --verify refs/bisect/bad || missing_bad=t @@ -190,7 +191,7 @@ bisect_next_check() { ;; *) THEN='' - test -d "$GIT_DIR/refs/bisect" || { + test -f "$GIT_DIR/BISECT_NAMES" || { echo >&2 'You need to start by "git bisect start".' THEN='then ' } @@ -206,17 +207,97 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } +filter_skipped() { + _eval="$1" + _skip="$2" + + if [ -z "$_skip" ]; then + eval $_eval + return + fi + + # Let's parse the output of: + # "git rev-list --bisect-vars --bisect-all ..." + eval $_eval | while read hash line + do + case "$VARS,$FOUND,$TRIED,$hash" in + # We display some vars. + 1,*,*,*) echo "$hash $line" ;; + + # Split line. + ,*,*,---*) ;; + + # We had nothing to search. + ,,,bisect_rev*) + echo "bisect_rev=" + VARS=1 + ;; + + # We did not find a good bisect rev. + # This should happen only if the "bad" + # commit is also a "skip" commit. + ,,*,bisect_rev*) + echo "bisect_rev=$TRIED" + VARS=1 + ;; + + # We are searching. + ,,*,*) + TRIED="${TRIED:+$TRIED|}$hash" + case "$_skip" in + *$hash*) ;; + *) + echo "bisect_rev=$hash" + echo "bisect_tried=\"$TRIED\"" + FOUND=1 + ;; + esac + ;; + + # We have already found a rev to be tested. + ,1,*,bisect_rev*) VARS=1 ;; + ,1,*,*) ;; + + # ??? + *) die "filter_skipped error " \ + "VARS: '$VARS' " \ + "FOUND: '$FOUND' " \ + "TRIED: '$TRIED' " \ + "hash: '$hash' " \ + "line: '$line'" + ;; + esac + done +} + +exit_if_skipped_commits () { + _tried=$1 + if expr "$_tried" : ".*[|].*" > /dev/null ; then + echo "There are only 'skip'ped commit left to test." + echo "The first bad commit could be any of:" + echo "$_tried" | tr '[|]' '[\012]' + echo "We cannot bisect more!" + exit 2 + fi +} + bisect_next() { - case "$#" in 0) ;; *) usage ;; esac + case "$#" in 0) ;; *) usage ;; esac bisect_autostart bisect_next_check good + skip=$(git for-each-ref --format='%(objectname)' \ + "refs/bisect/skip-*" | tr '[\012]' ' ') || exit + + BISECT_OPT='' + test -n "$skip" && BISECT_OPT='--bisect-all' + bad=$(git rev-parse --verify refs/bisect/bad) && good=$(git for-each-ref --format='^%(objectname)' \ "refs/bisect/good-*" | tr '[\012]' ' ') && - eval="git rev-list --bisect-vars $good $bad --" && + eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" && eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && - eval=$(eval "$eval") && + eval=$(filter_skipped "$eval" "$skip") && eval "$eval" || exit if [ -z "$bisect_rev" ]; then @@ -224,38 +305,44 @@ bisect_next() { exit 1 fi if [ "$bisect_rev" = "$bad" ]; then + exit_if_skipped_commits "$bisect_tried" echo "$bisect_rev is first bad commit" git diff-tree --pretty $bisect_rev exit 0 fi + # We should exit here only if the "bad" + # commit is also a "skip" commit (see above). + exit_if_skipped_commits "$bisect_rev" + echo "Bisecting: $bisect_nr revisions left to test after this" - echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect" + git branch -f new-bisect "$bisect_rev" git checkout -q new-bisect || exit - mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && - GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect + git branch -M new-bisect bisect git show-branch "$bisect_rev" } bisect_visualize() { bisect_next_check fail - not=`cd "$GIT_DIR/refs" && echo bisect/good-*` - eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES") + not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*") + eval gitk refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES") } bisect_reset() { + test -f "$GIT_DIR/BISECT_NAMES" || { + echo "We are not bisecting." + return + } case "$#" in 0) if [ -s "$GIT_DIR/head-name" ]; then branch=`cat "$GIT_DIR/head-name"` else branch=master fi ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || { - echo >&2 "$1 does not seem to be a valid branch" - exit 1 - } + 1) git show-ref --verify --quiet -- "refs/heads/$1" || + die "$1 does not seem to be a valid branch" branch="$1" ;; - *) + *) usage ;; esac if git checkout "$branch"; then @@ -265,18 +352,19 @@ bisect_reset() { } bisect_clean_state() { - rm -fr "$GIT_DIR/refs/bisect" - rm -f "$GIT_DIR/refs/heads/bisect" + # There may be some refs packed during bisection. + git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect | + while read ref hash + do + git update-ref -d $ref $hash + done rm -f "$GIT_DIR/BISECT_LOG" rm -f "$GIT_DIR/BISECT_NAMES" rm -f "$GIT_DIR/BISECT_RUN" } bisect_replay () { - test -r "$1" || { - echo >&2 "cannot read $1 for replaying" - exit 1 - } + test -r "$1" || die "cannot read $1 for replaying" bisect_reset while read bisect command rev do @@ -284,21 +372,11 @@ bisect_replay () { case "$command" in start) cmd="bisect_start $rev" - eval "$cmd" - ;; - good) - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - ;; - bad) - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - ;; + eval "$cmd" ;; + good|bad|skip) + bisect_write "$command" "$rev" ;; *) - echo >&2 "?? what are you talking about?" - exit 1 ;; + die "?? what are you talking about?" ;; esac done <"$1" bisect_auto_next @@ -320,24 +398,31 @@ bisect_run () { exit $res fi - # Use "bisect_good" or "bisect_bad" - # depending on run success or failure. - if [ $res -gt 0 ]; then - next_bisect='bisect_bad' + # Find current state depending on run success or failure. + # A special exit code of 125 means cannot test. + if [ $res -eq 125 ]; then + state='skip' + elif [ $res -gt 0 ]; then + state='bad' else - next_bisect='bisect_good' + state='good' fi - # We have to use a subshell because bisect_good or - # bisect_bad functions can exit. - ( $next_bisect > "$GIT_DIR/BISECT_RUN" ) + # We have to use a subshell because "bisect_state" can exit. + ( bisect_state $state > "$GIT_DIR/BISECT_RUN" ) res=$? cat "$GIT_DIR/BISECT_RUN" + if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ + > /dev/null; then + echo >&2 "bisect run cannot continue any more" + exit $res + fi + if [ $res -ne 0 ]; then echo >&2 "bisect run failed:" - echo >&2 "$next_bisect exited with error code $res" + echo >&2 "'bisect_state $state' exited with error code $res" exit $res fi @@ -359,10 +444,8 @@ case "$#" in case "$cmd" in start) bisect_start "$@" ;; - bad) - bisect_bad "$@" ;; - good) - bisect_good "$@" ;; + bad|good|skip) + bisect_state "$cmd" "$@" ;; next) # Not sure we want "next" at the UI level anymore. bisect_next "$@" ;; diff --git a/git-checkout.sh b/git-checkout.sh index 5ca71242e7..f6d58ac044 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -1,6 +1,16 @@ #!/bin/sh -USAGE='[-q] [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]' +OPTIONS_KEEPDASHDASH=t +OPTIONS_SPEC="\ +git-branch [options] [<branch>] [<paths>...] +-- +b= create a new branch started at <branch> +l create the new branchs reflog +track tells if the new branch should track the remote branch +f proceed even if the index or working tree is not HEAD +m performa three-way merge on local modifications if needed +q,quiet be quiet +" SUBDIRECTORY_OK=Sometimes . git-sh-setup require_work_tree @@ -20,13 +30,12 @@ quiet= v=-v LF=' ' -while [ "$#" != "0" ]; do - arg="$1" - shift - case "$arg" in - "-b") - newbranch="$1" + +while test $# != 0; do + case "$1" in + -b) shift + newbranch="$1" [ -z "$newbranch" ] && die "git checkout: -b needs a branch name" git show-ref --verify --quiet -- "refs/heads/$newbranch" && @@ -34,64 +43,54 @@ while [ "$#" != "0" ]; do git check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." ;; - "-l") + -l) newbranch_log=-l ;; - "--track"|"--no-track") - track="$arg" + --track|--no-track) + track="$1" ;; - "-f") + -f) force=1 ;; -m) merge=1 ;; - "-q") + -q|--quiet) quiet=1 v= ;; --) + shift break ;; - -*) - usage - ;; *) - if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null) - then - if [ -z "$rev" ]; then - echo "unknown flag $arg" - exit 1 - fi - new_name="$arg" - if git show-ref --verify --quiet -- "refs/heads/$arg" - then - rev=$(git rev-parse --verify "refs/heads/$arg^0") - branch="$arg" - fi - new="$rev" - elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null) - then - # checking out selected paths from a tree-ish. - new="$rev" - new_name="$arg^{tree}" - branch= - else - new= - new_name= - branch= - set x "$arg" "$@" - shift - fi - case "$1" in - --) - shift ;; - esac - break + usage ;; - esac + esac + shift done +arg="$1" +if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null) +then + [ -z "$rev" ] && die "unknown flag $arg" + new_name="$arg" + if git show-ref --verify --quiet -- "refs/heads/$arg" + then + rev=$(git rev-parse --verify "refs/heads/$arg^0") + branch="$arg" + fi + new="$rev" + shift +elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null) +then + # checking out selected paths from a tree-ish. + new="$rev" + new_name="$arg^{tree}" + shift +fi +[ "$1" = "--" ] && shift + case "$newbranch,$track" in ,--*) die "git checkout: --track and --no-track require -b" @@ -134,9 +133,16 @@ Did you intend to checkout '$@' which can not be resolved as commit?" fi # Make sure the request is about existing paths. - git ls-files --error-unmatch -- "$@" >/dev/null || exit - git ls-files -- "$@" | - git checkout-index -f -u --stdin + git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit + git ls-files --full-name -- "$@" | + (cd_to_toplevel && git checkout-index -f -u --stdin) + + # Run a post-checkout hook -- the HEAD does not change so the + # current HEAD is passed in for both args + if test -x "$GIT_DIR"/hooks/post-checkout; then + "$GIT_DIR"/hooks/post-checkout $old $old 0 + fi + exit $? else # Make sure we did not fall back on $arg^{tree} codepath @@ -260,7 +266,7 @@ if [ "$?" -eq 0 ]; then if test -n "$branch" then old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'` - GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from $old_branch_name to $branch" HEAD "refs/heads/$branch" + GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch" if test -n "$quiet" then true # nothing @@ -272,7 +278,8 @@ if [ "$?" -eq 0 ]; then fi elif test -n "$detached" then - git update-ref --no-deref -m "checkout: moving to $arg" HEAD "$detached" || + old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'` + git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" || die "Cannot detach HEAD" if test -n "$detach_warn" then @@ -284,3 +291,8 @@ if [ "$?" -eq 0 ]; then else exit 1 fi + +# Run a post-checkout hook +if test -x "$GIT_DIR"/hooks/post-checkout; then + "$GIT_DIR"/hooks/post-checkout $old $new 1 +fi diff --git a/git-clone.sh b/git-clone.sh index 0ea3c24f59..24ad179bbd 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -8,15 +8,36 @@ # See git-sh-setup why. unset CDPATH +OPTIONS_SPEC="\ +git-clone [options] [--] <repo> [<dir>] +-- +n,no-checkout don't create a checkout +bare create a bare repository +naked create a bare repository +l,local to clone from a local repository +no-hardlinks don't use local hardlinks, always copy +s,shared setup as a shared repository +template= path to the template directory +q,quiet be quiet +reference= reference repository +o,origin= use <name> instead of 'origin' to track upstream +u,upload-pack= path to git-upload-pack on the remote +depth= create a shallow clone of that depth + +use-separate-remote compatibility, do not use +no-separate-remote compatibility, do not use" + die() { echo >&2 "$@" exit 1 } usage() { - die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]" + exec "$0" -h } +eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" + get_repo_base() { ( cd "`/bin/pwd`" && @@ -106,64 +127,57 @@ depth= no_progress= local_explicitly_asked_for= test -t 1 || no_progress=--no-progress -while - case "$#,$1" in - 0,*) break ;; - *,-n|*,--no|*,--no-|*,--no-c|*,--no-ch|*,--no-che|*,--no-chec|\ - *,--no-check|*,--no-checko|*,--no-checkou|*,--no-checkout) - no_checkout=yes ;; - *,--na|*,--nak|*,--nake|*,--naked|\ - *,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;; - *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) - local_explicitly_asked_for=yes - use_local_hardlink=yes ;; - *,--no-h|*,--no-ha|*,--no-har|*,--no-hard|*,--no-hardl|\ - *,--no-hardli|*,--no-hardlin|*,--no-hardlink|*,--no-hardlinks) - use_local_hardlink=no ;; - *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) - local_shared=yes; ;; - 1,--template) usage ;; - *,--template) + +while test $# != 0 +do + case "$1" in + -n|--no-checkout) + no_checkout=yes ;; + --naked|--bare) + bare=yes ;; + -l|--local) + local_explicitly_asked_for=yes + use_local_hardlink=yes + ;; + --no-hardlinks) + use_local_hardlink=no ;; + -s|--shared) + local_shared=yes ;; + --template) shift; template="--template=$1" ;; - *,--template=*) - template="$1" ;; - *,-q|*,--quiet) quiet=-q ;; - *,--use-separate-remote) ;; - *,--no-separate-remote) + -q|--quiet) + quiet=-q ;; + --use-separate-remote|--no-separate-remote) die "clones are always made with separate-remote layout" ;; - 1,--reference) usage ;; - *,--reference) + --reference) shift; reference="$1" ;; - *,--reference=*) - reference=`expr "z$1" : 'z--reference=\(.*\)'` ;; - *,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin) - case "$2" in + -o,--origin) + shift; + case "$1" in '') usage ;; */*) - die "'$2' is not suitable for an origin name" + die "'$1' is not suitable for an origin name" esac - git check-ref-format "heads/$2" || - die "'$2' is not suitable for a branch name" + git check-ref-format "heads/$1" || + die "'$1' is not suitable for a branch name" test -z "$origin_override" || die "Do not give more than one --origin options." origin_override=yes - origin="$2"; shift + origin="$1" ;; - 1,-u|1,--upload-pack) usage ;; - *,-u|*,--upload-pack) + -u|--upload-pack) shift upload_pack="--upload-pack=$1" ;; - *,--upload-pack=*) - upload_pack=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; - 1,--depth) usage;; - *,--depth) + --depth) + shift + depth="--depth=$1" ;; + --) shift - depth="--depth=$1";; - *,-*) usage ;; - *) break ;; + break ;; + *) + usage ;; esac -do shift done diff --git a/git-commit.sh b/git-commit.sh index 1c0c6b9e41..485339754c 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -5,6 +5,7 @@ USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]' SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= . git-sh-setup require_work_tree @@ -99,101 +100,71 @@ do no_edit=t log_given=t$log_given logfile="$1" - shift ;; -F*|-f*) no_edit=t log_given=t$log_given - logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` - shift + logfile="${1#-[Ff]}" ;; --F=*|--f=*|--fi=*|--fil=*|--file=*) no_edit=t log_given=t$log_given - logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift + logfile="${1#*=}" ;; -a|--a|--al|--all) all=t - shift ;; --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift + force_author="${1#*=}" ;; --au|--aut|--auth|--autho|--author) case "$#" in 1) usage ;; esac shift force_author="$1" - shift ;; -e|--e|--ed|--edi|--edit) edit_flag=t - shift ;; -i|--i|--in|--inc|--incl|--inclu|--includ|--include) also=t - shift ;; --int|--inte|--inter|--intera|--interac|--interact|--interacti|\ --interactiv|--interactive) interactive=t - shift ;; -o|--o|--on|--onl|--only) only=t - shift ;; -m|--m|--me|--mes|--mess|--messa|--messag|--message) case "$#" in 1) usage ;; esac shift log_given=m$log_given - if test "$log_message" = '' - then - log_message="$1" - else - log_message="$log_message + log_message="${log_message:+${log_message} -$1" - fi +}$1" no_edit=t - shift ;; -m*) log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-m\(.*\)'` - else - log_message="$log_message + log_message="${log_message:+${log_message} -`expr "z$1" : 'z-m\(.*\)'`" - fi +}${1#-m}" no_edit=t - shift ;; --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` - else - log_message="$log_message + log_message="${log_message:+${log_message} -`expr "z$1" : 'zq-[^=]*=\(.*\)'`" - fi +}${1#*=}" no_edit=t - shift ;; -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ --no-verify) verify= - shift ;; --a|--am|--ame|--amen|--amend) amend=t use_commit=HEAD - shift ;; -c) case "$#" in 1) usage ;; esac @@ -201,15 +172,13 @@ $1" log_given=t$log_given use_commit="$1" no_edit= - shift ;; --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ --reedit-messag=*|--reedit-message=*) log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + use_commit="${1#*=}" no_edit= - shift ;; --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ @@ -219,7 +188,6 @@ $1" log_given=t$log_given use_commit="$1" no_edit= - shift ;; -C) case "$#" in 1) usage ;; esac @@ -227,15 +195,13 @@ $1" log_given=t$log_given use_commit="$1" no_edit=t - shift ;; --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ --reuse-message=*) log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + use_commit="${1#*=}" no_edit=t - shift ;; --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) @@ -244,32 +210,26 @@ $1" log_given=t$log_given use_commit="$1" no_edit=t - shift ;; -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) signoff=t - shift ;; -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template) case "$#" in 1) usage ;; esac shift templatefile="$1" no_edit= - shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t - shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t - shift ;; -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ --untracked-file|--untracked-files) untracked_files=t - shift ;; --) shift @@ -282,6 +242,7 @@ $1" break ;; esac + shift done case "$edit_flag" in t) no_edit= ;; esac @@ -442,12 +403,8 @@ esac if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit then - if test "$TMP_INDEX" - then - GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit - else - GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit - fi || exit + GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \ + || exit fi if test "$log_message" != '' @@ -656,6 +613,7 @@ git rerere if test "$ret" = 0 then + git gc --auto if test -x "$GIT_DIR"/hooks/post-commit then "$GIT_DIR"/hooks/post-commit diff --git a/git-compat-util.h b/git-compat-util.h index ca0a597a28..79eb10eacb 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -4,10 +4,24 @@ #define _FILE_OFFSET_BITS 64 #ifndef FLEX_ARRAY -#if defined(__GNUC__) && (__GNUC__ < 3) -#define FLEX_ARRAY 0 -#else -#define FLEX_ARRAY /* empty */ +/* + * See if our compiler is known to support flexible array members. + */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define FLEX_ARRAY /* empty */ +#elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define FLEX_ARRAY /* empty */ +# else +# define FLEX_ARRAY 0 /* older GNU extension */ +# endif +#endif + +/* + * Otherwise, default to safer but a bit wasteful traditional style + */ +#ifndef FLEX_ARRAY +# define FLEX_ARRAY 1 #endif #endif @@ -20,6 +34,7 @@ #endif #define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits)))) +#define HAS_MULTI_BITS(i) ((i) & ((i) - 1)) /* checks if an integer has more than 1 bit set */ /* Approximation of the length of the decimal representation of this type. */ #define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) @@ -52,6 +67,8 @@ #include <fnmatch.h> #include <sys/poll.h> #include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/select.h> #include <assert.h> #include <regex.h> #include <netinet/in.h> @@ -147,6 +164,11 @@ extern ssize_t git_pread(int fd, void *buf, size_t count, off_t offset); extern int gitsetenv(const char *, const char *, int); #endif +#ifdef NO_MKDTEMP +#define mkdtemp gitmkdtemp +extern char *gitmkdtemp(char *); +#endif + #ifdef NO_UNSETENV #define unsetenv gitunsetenv extern void gitunsetenv(const char *); @@ -172,6 +194,28 @@ extern uintmax_t gitstrtoumax(const char *, char **, int); extern const char *githstrerror(int herror); #endif +#ifdef NO_MEMMEM +#define memmem gitmemmem +void *gitmemmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +#endif + +#ifdef __GLIBC_PREREQ +#if __GLIBC_PREREQ(2, 1) +#define HAVE_STRCHRNUL +#endif +#endif + +#ifndef HAVE_STRCHRNUL +#define strchrnul gitstrchrnul +static inline char *gitstrchrnul(const char *s, int c) +{ + while (*s && *s != c) + s++; + return (char *)s; +} +#endif + extern void release_pack_memory(size_t, int); static inline char* xstrdup(const char *str) @@ -205,19 +249,20 @@ static inline void *xmalloc(size_t size) return ret; } -static inline char *xstrndup(const char *str, size_t len) +static inline void *xmemdupz(const void *data, size_t len) { - char *p; - - p = memchr(str, '\0', len); - if (p) - len = p - str; - p = xmalloc(len + 1); - memcpy(p, str, len); + char *p = xmalloc(len + 1); + memcpy(p, data, len); p[len] = '\0'; return p; } +static inline char *xstrndup(const char *str, size_t len) +{ + char *p = memchr(str, '\0', len); + return xmemdupz(str, p ? p - str : len); +} + static inline void *xrealloc(void *ptr, size_t size) { void *ret = realloc(ptr, size); @@ -369,4 +414,17 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result) return 0; } +static inline int strtol_i(char const *s, int base, int *result) +{ + long ul; + char *p; + + errno = 0; + ul = strtol(s, &p, base); + if (errno || *p || p == s || (int) ul != ul) + return -1; + *result = ul; + return 0; +} + #endif diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index b2c9f98e86..92e41620fd 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,28 +1,42 @@ #!/usr/bin/perl -w -# Known limitations: -# - does not propagate permissions -# - error handling has not been extensively tested -# - use strict; use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; use File::Basename qw(basename dirname); -unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ - die "GIT_DIR is not defined or is unreadable"; -} - -our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w); -getopts('uhPpvcfam:d:'); +getopts('uhPpvcfam:d:w:'); $opt_h && usage(); die "Need at least one commit identifier!" unless @ARGV; +if ($opt_w) { + unless ($ENV{GIT_DIR}) { + # Remember where our GIT_DIR is before changing to CVS checkout + my $gd =`git-rev-parse --git-dir`; + chomp($gd); + if ($gd eq '.git') { + my $wd = `pwd`; + chomp($wd); + $gd = $wd."/.git" ; + } + $ENV{GIT_DIR} = $gd; + } + + if (! -d $opt_w."/CVS" ) { + die "$opt_w is not a CVS checkout"; + } + chdir $opt_w or die "Cannot change to CVS checkout at $opt_w"; +} +unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ + die "GIT_DIR is not defined or is unreadable"; +} + + my @cvs; if ($opt_d) { @cvs = ('cvs', '-d', $opt_d); @@ -30,11 +44,6 @@ if ($opt_d) { @cvs = ('cvs'); } -# setup a tempdir -our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX', - TMPDIR => 1, - CLEANUP => 1); - # resolve target commit my $commit; $commit = pop @ARGV; @@ -141,7 +150,7 @@ my $context = $opt_p ? '' : '-C1'; print "Checking if patch will apply\n"; my @stat; -open APPLY, "GIT_DIR= git-apply $context --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; +open APPLY, "GIT_DIR= git-apply $context --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; @stat=<APPLY>; close APPLY || die "Cannot patch"; my (@bfiles,@files,@afiles,@dfiles); @@ -227,7 +236,7 @@ if ($dirty) { } print "Applying\n"; -`GIT_DIR= git-apply $context --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; +`GIT_DIR= git-apply $context --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; print "Patch applied successfully. Adding new files and directories to CVS\n"; my $dirtypatch = 0; @@ -279,6 +288,7 @@ if ($dirtypatch) { print "You'll need to apply the patch in .cvsexportcommit.diff manually\n"; print "using a patch program. After applying the patch and resolving the\n"; print "problems you may commit using:"; + print "\n cd \"$opt_w\"" if $opt_w; print "\n $cmd\n\n"; exit(1); } @@ -306,7 +316,7 @@ sleep(1); sub usage { print STDERR <<END; -Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit +Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit END exit(1); } diff --git a/git-cvsimport.perl b/git-cvsimport.perl index e4bc2b54f6..efa6a0c41a 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -223,7 +223,8 @@ sub conn { } } - $user="anonymous" unless defined $user; + # if username is not explicit in CVSROOT, then use current user, as cvs would + $user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user; my $rr2 = "-"; unless ($port) { $rr2 = ":pserver:$user\@$serv:$repo"; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index dbab1a9a4a..674a25d27e 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -8,6 +8,9 @@ # a new branch. You can specify a number of filters to modify the commits, # files and trees. +# The following functions will also be available in the commit filter: + +functions=$(cat << \EOF warn () { echo "$*" >&2 } @@ -46,6 +49,10 @@ die() echo "$*" >&2 exit 1 } +EOF +) + +eval "$functions" # When piped a commit, output a script to set the ident of either # "author" or "committer @@ -80,11 +87,6 @@ set_ident () { echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\"" } -# This script can be sourced by the commit filter to get the functions -test "a$SOURCE_FUNCTIONS" = a1 && return -this_script="$(cd "$(dirname "$0")"; pwd)"/$(basename "$0") -export this_script - USAGE="[--env-filter <command>] [--tree-filter <command>] \ [--index-filter <command>] [--parent-filter <command>] \ [--msg-filter <command>] [--commit-filter <command>] \ @@ -92,6 +94,7 @@ USAGE="[--env-filter <command>] [--tree-filter <command>] \ [--original <namespace>] [-d <directory>] [-f | --force] \ [<rev-list options>...]" +OPTIONS_SPEC= . git-sh-setup git diff-files --quiet && @@ -155,7 +158,7 @@ do filter_msg="$OPTARG" ;; --commit-filter) - filter_commit='SOURCE_FUNCTIONS=1 . "$this_script";'" $OPTARG" + filter_commit="$functions; $OPTARG" ;; --tag-name-filter) filter_tag_name="$OPTARG" diff --git a/git-gui/.gitignore b/git-gui/.gitignore index 020b86deae..6483b21cbf 100644 --- a/git-gui/.gitignore +++ b/git-gui/.gitignore @@ -1,5 +1,8 @@ +.DS_Store +config.mak +Git Gui.app* +git-gui.tcl GIT-VERSION-FILE GIT-GUI-VARS -git-citool git-gui lib/tclIndex diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN index 9770b0bc27..cfe46a857e 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=0.8.GITGUI +DEF_VER=0.9.GITGUI LF=' ' diff --git a/git-gui/Makefile b/git-gui/Makefile index 18e6750137..e860319278 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -2,18 +2,27 @@ all:: # Define V=1 to have a more verbose compile. # +# Define NO_MSGFMT if you do not have msgfmt from the GNU gettext +# package and want to use our rough pure Tcl po->msg translator. +# TCL_PATH must be vaild for this to work. +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') SCRIPT_SH = git-gui.sh +GITGUI_MAIN := git-gui GITGUI_BUILT_INS = git-citool -ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) ALL_LIBFILES = $(wildcard lib/*.tcl) PRELOAD_FILES = lib/class.tcl +NONTCL_LIBFILES = \ + lib/git-gui.ico \ + $(wildcard lib/win32_*.js) \ +#end NONTCL_LIBFILES ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -31,7 +40,7 @@ ifndef INSTALL INSTALL = install endif -RM_F ?= rm -f +RM_RF ?= rm -rf RMDIR ?= rmdir INSTALL_D0 = $(INSTALL) -d -m755 # space is required here @@ -40,6 +49,8 @@ INSTALL_R0 = $(INSTALL) -m644 # space is required here INSTALL_R1 = INSTALL_X0 = $(INSTALL) -m755 # space is required here INSTALL_X1 = +INSTALL_A0 = find # space is required here +INSTALL_A1 = | cpio -pud INSTALL_L0 = rm -f # space is required here INSTALL_L1 = && ln # space is required here INSTALL_L2 = @@ -47,15 +58,16 @@ INSTALL_L3 = REMOVE_D0 = $(RMDIR) # space is required here REMOVE_D1 = || true -REMOVE_F0 = $(RM_F) # space is required here +REMOVE_F0 = $(RM_RF) # space is required here REMOVE_F1 = CLEAN_DST = true ifndef V QUIET = @ - QUIET_GEN = $(QUIET)echo ' ' GEN $@ && - QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ && + QUIET_GEN = $(QUIET)echo ' ' GEN '$@' && QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) && + QUIET_MSGFMT0 = $(QUIET)printf ' MSGFMT %12s ' $@ && v=` + QUIET_MSGFMT1 = 2>&1` && echo "$$v" | sed -e 's/fuzzy translations/fuzzy/' | sed -e 's/ messages//g' QUIET_2DEVNULL = 2>/dev/null INSTALL_D0 = dir= @@ -64,6 +76,8 @@ ifndef V INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src INSTALL_X0 = src= INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src + INSTALL_A0 = src= + INSTALL_A1 = && echo ' ' INSTALL ' ' `basename "$$src"` && find "$$src" | cpio -pud INSTALL_L0 = dst= INSTALL_L1 = && src= @@ -74,51 +88,133 @@ ifndef V REMOVE_D0 = dir= REMOVE_D1 = && echo ' ' REMOVE $$dir && test -d "$$dir" && $(RMDIR) "$$dir" || true REMOVE_F0 = dst= - REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_F) "$$dst" + REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst" endif TCL_PATH ?= tclsh TCLTK_PATH ?= wish +TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app ifeq ($(findstring $(MAKEFLAGS),s),s) QUIET_GEN = -QUIET_BUILT_IN = endif +-include config.mak + DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) +TCLTK_PATH_SED = $(subst ','\'',$(subst \,\\,$(TCLTK_PATH))) gg_libdir ?= $(sharedir)/git-gui/lib libdir_SQ = $(subst ','\'',$(gg_libdir)) +libdir_SED = $(subst ','\'',$(subst \,\\,$(gg_libdir))) +exedir = $(dir $(gitexecdir))share/git-gui/lib + +GITGUI_SCRIPT := $$0 +GITGUI_RELATIVE := +GITGUI_MACOSXAPP := + +ifeq ($(exedir),$(gg_libdir)) + GITGUI_RELATIVE := 1 +endif + +ifeq ($(uname_O),Cygwin) + GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"` + ifeq ($(GITGUI_RELATIVE),) + gg_libdir := $(shell cygpath --windows --absolute "$(gg_libdir)") + endif +endif +ifeq ($(uname_S),Darwin) + ifeq ($(shell test -d $(TKFRAMEWORK) && echo y),y) + GITGUI_MACOSXAPP := YesPlease + endif +endif +ifneq (,$(findstring MINGW,$(uname_S))) + NO_MSGFMT=1 + GITGUI_WINDOWS_WRAPPER := YesPlease +endif + +ifdef GITGUI_MACOSXAPP +GITGUI_MAIN := git-gui.tcl + +git-gui: GIT-VERSION-FILE GIT-GUI-VARS + $(QUIET_GEN)rm -f $@ $@+ && \ + echo '#!$(SHELL_PATH_SQ)' >$@+ && \ + echo 'if test "z$$*" = zversion ||' >>$@+ && \ + echo ' test "z$$*" = z--version' >>$@+ && \ + echo then >>$@+ && \ + echo ' 'echo \'git-gui version '$(GITGUI_VERSION)'\' >>$@+ && \ + echo else >>$@+ && \ + echo ' 'exec \''$(libdir_SQ)/Git Gui.app/Contents/MacOS/Wish'\' \ + '"$$0" "$$@"' >>$@+ && \ + echo fi >>$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ + +Git\ Gui.app: GIT-VERSION-FILE GIT-GUI-VARS \ + macosx/Info.plist \ + macosx/git-gui.icns \ + macosx/AppMain.tcl \ + $(TKFRAMEWORK)/Contents/MacOS/Wish + $(QUIET_GEN)rm -rf '$@' '$@'+ && \ + mkdir -p '$@'+/Contents/MacOS && \ + mkdir -p '$@'+/Contents/Resources/Scripts && \ + cp '$(subst ','\'',$(TKFRAMEWORK))/Contents/MacOS/Wish' \ + '$@'+/Contents/MacOS && \ + cp macosx/git-gui.icns '$@'+/Contents/Resources && \ + sed -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + macosx/Info.plist \ + >'$@'+/Contents/Info.plist && \ + sed -e 's|@@gitexecdir@@|$(gitexecdir_SQ)|' \ + -e 's|@@GITGUI_LIBDIR@@|$(libdir_SED)|' \ + macosx/AppMain.tcl \ + >'$@'+/Contents/Resources/Scripts/AppMain.tcl && \ + mv '$@'+ '$@' +endif + +ifdef GITGUI_WINDOWS_WRAPPER +GITGUI_MAIN := git-gui.tcl -exedir = $(dir $(gitexecdir))share/git-gui/lib -exedir_SQ = $(subst ','\'',$(exedir)) +git-gui: windows/git-gui.sh + cp $< $@ +endif -$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh +$(GITGUI_MAIN): git-gui.sh GIT-VERSION-FILE GIT-GUI-VARS $(QUIET_GEN)rm -f $@ $@+ && \ - GITGUI_RELATIVE= && \ - if test '$(exedir_SQ)' = '$(libdir_SQ)'; then \ - if test "$(uname_O)" = Cygwin; \ - then GITGUI_RELATIVE= ; \ - else GITGUI_RELATIVE=1; \ - fi; \ - fi && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|^ exec wish "$$0"| exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ + -e '1,30s|^ argv0=$$0| argv0=$(GITGUI_SCRIPT)|' \ + -e '1,30s|^ exec wish | exec '\''$(TCLTK_PATH_SED)'\'' |' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ - -e 's|@@GITGUI_RELATIVE@@|'$$GITGUI_RELATIVE'|' \ - -e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ - $@.sh >$@+ && \ + -e 's|@@GITGUI_RELATIVE@@|$(GITGUI_RELATIVE)|' \ + -e '$(GITGUI_RELATIVE)s|@@GITGUI_LIBDIR@@|$(libdir_SED)|' \ + git-gui.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -$(GITGUI_BUILT_INS): git-gui - $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ +XGETTEXT ?= xgettext +ifdef NO_MSGFMT + MSGFMT ?= $(TCL_PATH) po/po2msg.sh +else + MSGFMT ?= msgfmt +endif + +msgsdir = $(gg_libdir)/msgs +msgsdir_SQ = $(subst ','\'',$(msgsdir)) +PO_TEMPLATE = po/git-gui.pot +ALL_POFILES = $(wildcard po/*.po) +ALL_MSGFILES = $(subst .po,.msg,$(ALL_POFILES)) + +$(PO_TEMPLATE): $(SCRIPT_SH) $(ALL_LIBFILES) + $(XGETTEXT) -kmc -LTcl -o $@ $(SCRIPT_SH) $(ALL_LIBFILES) +update-po:: $(PO_TEMPLATE) + $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; ) +$(ALL_MSGFILES): %.msg : %.po + $(QUIET_MSGFMT0)$(MSGFMT) --statistics --tcl $< -l $(basename $(notdir $<)) -d $(dir $@) $(QUIET_MSGFMT1) -lib/tclIndex: $(ALL_LIBFILES) +lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS $(QUIET_INDEX)if echo \ $(foreach p,$(PRELOAD_FILES),source $p\;) \ auto_mkindex lib '*.tcl' \ @@ -132,16 +228,13 @@ lib/tclIndex: $(ALL_LIBFILES) echo >>$@ ; \ fi -# These can record GITGUI_VERSION -$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS -lib/tclIndex: GIT-GUI-VARS - TRACK_VARS = \ $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ $(subst ','\'',TCL_PATH='$(TCL_PATH_SQ)') \ $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ $(subst ','\'',gitexecdir='$(gitexecdir_SQ)') \ $(subst ','\'',gg_libdir='$(libdir_SQ)') \ + GITGUI_MACOSXAPP=$(GITGUI_MACOSXAPP) \ #end TRACK_VARS GIT-GUI-VARS: .FORCE-GIT-GUI-VARS @@ -151,24 +244,49 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS echo 1>$@ "$$VARS"; \ fi -all:: $(ALL_PROGRAMS) lib/tclIndex +ifdef GITGUI_MACOSXAPP +all:: git-gui Git\ Gui.app +endif +ifdef GITGUI_WINDOWS_WRAPPER +all:: git-gui +endif +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)$(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)' +endif $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1) $(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' - $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true +ifdef GITGUI_MACOSXAPP + $(QUIET)$(INSTALL_A0)'Git Gui.app' $(INSTALL_A1) '$(DESTDIR_SQ)$(libdir_SQ)' + $(QUIET)$(INSTALL_X0)git-gui.tcl $(INSTALL_X1) '$(DESTDIR_SQ)$(libdir_SQ)' +endif + $(QUIET)$(foreach p,$(ALL_LIBFILES) $(NONTCL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true + $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(msgsdir_SQ)' $(INSTALL_D1) + $(QUIET)$(foreach p,$(ALL_MSGFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true uninstall: $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(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) +endif $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(libdir_SQ)' $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/tclIndex $(REMOVE_F1) - $(QUIET)$(foreach p,$(ALL_LIBFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true +ifdef GITGUI_MACOSXAPP + $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)/Git Gui.app' $(REMOVE_F1) + $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/git-gui.tcl $(REMOVE_F1) +endif + $(QUIET)$(foreach p,$(ALL_LIBFILES) $(NONTCL_LIBFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true + $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(msgsdir_SQ)' + $(QUIET)$(foreach p,$(ALL_MSGFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(msgsdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true $(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(REMOVE_D1) + $(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(msgsdir_SQ)' $(REMOVE_D1) $(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(REMOVE_D1) $(QUIET)$(REMOVE_D0)`dirname '$(DESTDIR_SQ)$(libdir_SQ)'` $(REMOVE_D1) @@ -177,8 +295,14 @@ dist-version: @echo $(GITGUI_VERSION) > $(TARDIR)/version clean:: - rm -f $(ALL_PROGRAMS) lib/tclIndex - rm -f GIT-VERSION-FILE GIT-GUI-VARS + $(RM_RF) $(GITGUI_MAIN) lib/tclIndex po/*.msg + $(RM_RF) GIT-VERSION-FILE GIT-GUI-VARS +ifdef GITGUI_MACOSXAPP + $(RM_RF) 'Git Gui.app'* git-gui +endif +ifdef GITGUI_WINDOWS_WRAPPER + $(RM_RF) git-gui +endif .PHONY: all install uninstall dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 9335a9761b..1fca11f278 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -6,11 +6,12 @@ echo 'git-gui version @@GITGUI_VERSION@@'; \ exit; \ fi; \ - exec wish "$0" -- "$@" + argv0=$0; \ + exec wish "$argv0" -- "$@" set appvers {@@GITGUI_VERSION@@} -set copyright { -Copyright © 2006, 2007 Shawn Pearce, et. al. +set copyright [encoding convertfrom utf-8 { +Copyright © 2006, 2007 Shawn Pearce, et. al. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +25,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}] ###################################################################### ## @@ -37,7 +38,7 @@ if {[catch {package require Tcl 8.4} err] tk_messageBox \ -icon error \ -type ok \ - -title "git-gui: fatal error" \ + -title [mc "git-gui: fatal error"] \ -message $err exit 1 } @@ -46,6 +47,24 @@ catch {rename send {}} ; # What an evil concept... ###################################################################### ## +## locate our library + +set oguilib {@@GITGUI_LIBDIR@@} +set oguirel {@@GITGUI_RELATIVE@@} +if {$oguirel eq {1}} { + set oguilib [file dirname [file dirname [file normalize $argv0]]] + set oguilib [file join $oguilib share git-gui lib] + set oguimsg [file join $oguilib msgs] +} elseif {[string match @@* $oguirel]} { + set oguilib [file join [file dirname [file normalize $argv0]] lib] + set oguimsg [file join [file dirname [file normalize $argv0]] po] +} else { + set oguimsg [file join $oguilib msgs] +} +unset oguirel + +###################################################################### +## ## enable verbose loading? if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { @@ -64,21 +83,39 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { ###################################################################### ## -## Fake internationalization to ease backporting of changes. +## Internationalization (i18n) through msgcat and gettext. See +## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html + +package require msgcat -proc mc {fmt args} { +proc _mc_trim {fmt} { set cmk [string first @@ $fmt] if {$cmk > 0} { - set fmt [string range $fmt 0 [expr {$cmk - 1}]] + return [string range $fmt 0 [expr {$cmk - 1}]] } - return [eval [list format $fmt] $args] + return $fmt +} + +proc mc {en_fmt args} { + set fmt [_mc_trim [::msgcat::mc $en_fmt]] + if {[catch {set msg [eval [list format $fmt] $args]} err]} { + set msg [eval [list format [_mc_trim $en_fmt]] $args] + } + return $msg +} + +proc strcat {args} { + return [join $args {}] } +::msgcat::mcload $oguimsg +unset oguimsg + ###################################################################### ## ## read only globals -set _appname [lindex [file split $argv0] end] +set _appname {Git Gui} set _gitdir {} set _gitexec {} set _reponame {} @@ -175,6 +212,7 @@ proc disable_option {option} { proc is_many_config {name} { switch -glob -- $name { + gui.recentrepo - remote.*.fetch - remote.*.push {return 1} @@ -203,51 +241,6 @@ proc get_config {name} { } } -proc load_config {include_global} { - global repo_config global_config default_config - - array unset global_config - if {$include_global} { - catch { - set fd_rc [git_read config --global --list] - while {[gets $fd_rc line] >= 0} { - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { - if {[is_many_config $name]} { - lappend global_config($name) $value - } else { - set global_config($name) $value - } - } - } - close $fd_rc - } - } - - array unset repo_config - catch { - set fd_rc [git_read config --list] - while {[gets $fd_rc line] >= 0} { - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { - if {[is_many_config $name]} { - lappend repo_config($name) $value - } else { - set repo_config($name) $value - } - } - } - close $fd_rc - } - - foreach name [array names default_config] { - if {[catch {set v $global_config($name)}]} { - set global_config($name) $default_config($name) - } - if {[catch {set v $repo_config($name)}]} { - set repo_config($name) $default_config($name) - } - } -} - ###################################################################### ## ## handy utils @@ -313,6 +306,9 @@ proc _which {what} { $env(PATH)] {;}] set _search_exe .exe } elseif {[is_Windows]} { + set gitguidir [file dirname [info script]] + regsub -all ";" $gitguidir "\\;" gitguidir + set env(PATH) "$gitguidir;$env(PATH)" set _search_path [split $env(PATH) {;}] set _search_exe .exe } else { @@ -491,6 +487,110 @@ proc rmsel_tag {text} { return $text } +set root_exists 0 +bind . <Visibility> { + bind . <Visibility> {} + set root_exists 1 +} + +if {[is_Windows]} { + wm iconbitmap . -default $oguilib/git-gui.ico +} + +###################################################################### +## +## config defaults + +set cursor_ptr arrow +font create font_diff -family Courier -size 10 +font create font_ui +catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy +} + +font create font_uiitalic +font create font_uibold +font create font_diffbold +font create font_diffitalic + +foreach class {Button Checkbutton Entry Label + Labelframe Listbox Menu Message + Radiobutton Spinbox Text} { + option add *$class.font font_ui +} +unset class + +if {[is_Windows] || [is_MacOSX]} { + option add *Menu.tearOff 0 +} + +if {[is_MacOSX]} { + set M1B M1 + set M1T Cmd +} else { + set M1B Control + set M1T Ctrl +} + +proc bind_button3 {w cmd} { + bind $w <Any-Button-3> $cmd + if {[is_MacOSX]} { + # Mac OS X sends Button-2 on right click through three-button mouse, + # or through trackpad right-clicking (two-finger touch + click). + bind $w <Any-Button-2> $cmd + bind $w <Control-Button-1> $cmd + } +} + +proc apply_config {} { + global repo_config font_descs + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + if {[catch { + set need_weight 1 + foreach {cn cv} $repo_config(gui.$name) { + if {$cn eq {-weight}} { + set need_weight 0 + } + font configure $font $cn $cv + } + if {$need_weight} { + font configure $font -weight normal + } + } err]} { + error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"] + } + foreach {cn cv} [font configure $font] { + font configure ${font}bold $cn $cv + font configure ${font}italic $cn $cv + } + font configure ${font}bold -weight bold + font configure ${font}italic -slant italic + } +} + +set default_config(merge.diffstat) true +set default_config(merge.summary) false +set default_config(merge.verbosity) 2 +set default_config(user.name) {} +set default_config(user.email) {} + +set default_config(gui.matchtrackingbranch) false +set default_config(gui.pruneduringfetch) false +set default_config(gui.trustmtime) false +set default_config(gui.diffcontext) 5 +set default_config(gui.newbranchtemplate) {} +set default_config(gui.fontui) [font configure font_ui] +set default_config(gui.fontdiff) [font configure font_diff] +set font_descs { + {fontui font_ui {mc "Main Font"}} + {fontdiff font_diff {mc "Diff/Console Font"}} +} + ###################################################################### ## ## find git @@ -515,7 +615,7 @@ if {[catch {set _git_version [git --version]} err]} { tk_messageBox \ -icon error \ -type ok \ - -title "git-gui: fatal error" \ + -title [mc "git-gui: fatal error"] \ -message "Cannot determine Git version: $err @@ -528,8 +628,8 @@ if {![regsub {^git version } $_git_version {} _git_version]} { tk_messageBox \ -icon error \ -type ok \ - -title "git-gui: fatal error" \ - -message "Cannot parse Git version string:\n\n$_git_version" + -title [mc "git-gui: fatal error"] \ + -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"] exit 1 } @@ -547,14 +647,14 @@ if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { -type yesno \ -default no \ -title "[appname]: warning" \ - -message "Git version cannot be determined. + -message [mc "Git version cannot be determined. -$_git claims it is version '$_real_git_version'. +%s claims it is version '%s'. -[appname] requires at least Git 1.5.0 or later. +%s requires at least Git 1.5.0 or later. -Assume '$_real_git_version' is version 1.5.0? -"] eq {yes}} { +Assume '%s' is version 1.5.0? +" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} { set _git_version 1.5.0 } else { exit 1 @@ -611,7 +711,7 @@ if {[git-version < 1.5]} { tk_messageBox \ -icon error \ -type ok \ - -title "git-gui: fatal error" \ + -title [mc "git-gui: fatal error"] \ -message "[appname] requires Git 1.5.0 or later. You are using [git-version]: @@ -624,22 +724,13 @@ You are using [git-version]: ## ## configure our library -set oguilib {@@GITGUI_LIBDIR@@} -set oguirel {@@GITGUI_RELATIVE@@} -if {$oguirel eq {1}} { - set oguilib [file dirname [file dirname [file normalize $argv0]]] - set oguilib [file join $oguilib share git-gui lib] -} elseif {[string match @@* $oguirel]} { - set oguilib [file join [file dirname [file normalize $argv0]] lib] -} - set idx [file join $oguilib tclIndex] if {[catch {set fd [open $idx r]} err]} { catch {wm withdraw .} tk_messageBox \ -icon error \ -type ok \ - -title "git-gui: fatal error" \ + -title [mc "git-gui: fatal error"] \ -message $err exit 1 } @@ -666,13 +757,78 @@ if {$idx ne {}} { } else { set auto_path [concat [list $oguilib] $auto_path] } -unset -nocomplain oguirel idx fd +unset -nocomplain idx fd + +###################################################################### +## +## config file parsing + +git-version proc _parse_config {arr_name args} { + >= 1.5.3 { + upvar $arr_name arr + array unset arr + set buf {} + catch { + set fd_rc [eval \ + [list git_read config] \ + $args \ + [list --null --list]] + fconfigure $fd_rc -translation binary + set buf [read $fd_rc] + close $fd_rc + } + foreach line [split $buf "\0"] { + if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend arr($name) $value + } else { + set arr($name) $value + } + } + } + } + default { + upvar $arr_name arr + array unset arr + catch { + set fd_rc [eval [list git_read config --list] $args] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend arr($name) $value + } else { + set arr($name) $value + } + } + } + close $fd_rc + } + } +} + +proc load_config {include_global} { + global repo_config global_config default_config + + if {$include_global} { + _parse_config global_config --global + } + _parse_config repo_config + + foreach name [array names default_config] { + if {[catch {set v $global_config($name)}]} { + set global_config($name) $default_config($name) + } + if {[catch {set v $repo_config($name)}]} { + set repo_config($name) $default_config($name) + } + } +} ###################################################################### ## ## feature option selection -if {[regexp {^git-(.+)$} [appname] _junk subcommand]} { +if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} { unset _junk } else { set subcommand gui @@ -720,35 +876,35 @@ if {[catch { set _gitdir [git rev-parse --git-dir] set _prefix [git rev-parse --show-prefix] } err]} { - catch {wm withdraw .} - error_popup "Cannot find the git directory:\n\n$err" - exit 1 + load_config 1 + apply_config + choose_repository::pick } if {![file isdirectory $_gitdir] && [is_Cygwin]} { - catch {set _gitdir [exec cygpath --unix $_gitdir]} + catch {set _gitdir [exec cygpath --windows $_gitdir]} } if {![file isdirectory $_gitdir]} { catch {wm withdraw .} - error_popup "Git directory not found:\n\n$_gitdir" + error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] exit 1 } if {$_prefix ne {}} { regsub -all {[^/]+/} $_prefix ../ cdup if {[catch {cd $cdup} err]} { catch {wm withdraw .} - error_popup "Cannot move to top of working directory:\n\n$err" + error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"] exit 1 } unset cdup } elseif {![is_enabled bare]} { if {[lindex [file split $_gitdir] end] ne {.git}} { catch {wm withdraw .} - error_popup "Cannot use funny .git directory:\n\n$_gitdir" + error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"] exit 1 } if {[catch {cd [file dirname $_gitdir]} err]} { catch {wm withdraw .} - error_popup "No working directory [file dirname $_gitdir]:\n\n$err" + error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"] exit 1 } } @@ -895,7 +1051,7 @@ proc rescan {after {honor_trustmtime 1}} { rescan_stage2 {} $after } else { set rescan_active 1 - ui_status {Refreshing file status...} + ui_status [mc "Refreshing file status..."] set fd_rf [git_read update-index \ -q \ --unmerged \ @@ -960,7 +1116,7 @@ proc rescan_stage2 {fd after} { set buf_rlo {} set rescan_active 3 - ui_status {Scanning for modified files ...} + ui_status [mc "Scanning for modified files ..."] set fd_di [git_read diff-index --cached -z [PARENT]] set fd_df [git_read diff-files -z] set fd_lo [eval git_read ls-files --others -z $ls_others] @@ -1401,31 +1557,32 @@ set all_icons(O$ui_workdir) file_plain set max_status_desc 0 foreach i { - {__ "Unmodified"} - - {_M "Modified, not staged"} - {M_ "Staged for commit"} - {MM "Portions staged for commit"} - {MD "Staged for commit, missing"} - - {_O "Untracked, not staged"} - {A_ "Staged for commit"} - {AM "Portions staged for commit"} - {AD "Staged for commit, missing"} - - {_D "Missing"} - {D_ "Staged for removal"} - {DO "Staged for removal, still present"} - - {U_ "Requires merge resolution"} - {UU "Requires merge resolution"} - {UM "Requires merge resolution"} - {UD "Requires merge resolution"} + {__ {mc "Unmodified"}} + + {_M {mc "Modified, not staged"}} + {M_ {mc "Staged for commit"}} + {MM {mc "Portions staged for commit"}} + {MD {mc "Staged for commit, missing"}} + + {_O {mc "Untracked, not staged"}} + {A_ {mc "Staged for commit"}} + {AM {mc "Portions staged for commit"}} + {AD {mc "Staged for commit, missing"}} + + {_D {mc "Missing"}} + {D_ {mc "Staged for removal"}} + {DO {mc "Staged for removal, still present"}} + + {U_ {mc "Requires merge resolution"}} + {UU {mc "Requires merge resolution"}} + {UM {mc "Requires merge resolution"}} + {UD {mc "Requires merge resolution"}} } { - if {$max_status_desc < [string length [lindex $i 1]]} { - set max_status_desc [string length [lindex $i 1]] + set text [eval [lindex $i 1]] + if {$max_status_desc < [string length $text]} { + set max_status_desc [string length $text] } - set all_descs([lindex $i 0]) [lindex $i 1] + set all_descs([lindex $i 0]) $text } unset i @@ -1433,16 +1590,6 @@ unset i ## ## util -proc bind_button3 {w cmd} { - bind $w <Any-Button-3> $cmd - if {[is_MacOSX]} { - # Mac OS X sends Button-2 on right click through three-button mouse, - # or through trackpad right-clicking (two-finger touch + click). - bind $w <Any-Button-2> $cmd - bind $w <Control-Button-1> $cmd - } -} - proc scrollbar2many {list mode args} { foreach w $list {eval $w $mode $args} } @@ -1464,7 +1611,7 @@ proc incr_font_size {font {amt 1}} { ## ## ui commands -set starting_gitk_msg {Starting gitk... please wait...} +set starting_gitk_msg [mc "Starting gitk... please wait..."] proc do_gitk {revs} { # -- Always start gitk through whatever we were loaded with. This @@ -1473,7 +1620,7 @@ proc do_gitk {revs} { set exe [file join [file dirname $::_git] gitk] set cmd [list [info nameofexecutable] $exe] if {! [file exists $exe]} { - error_popup "Unable to start gitk:\n\n$exe does not exist" + error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe] } else { global env @@ -1546,8 +1693,8 @@ proc do_quit {} { # set cfg_geometry [list] lappend cfg_geometry [wm geometry .] - lappend cfg_geometry [lindex [.vpane sash coord 0] 1] - lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0] + lappend cfg_geometry [lindex [.vpane sash coord 0] 0] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1] if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { set rc_geometry {} } @@ -1664,104 +1811,26 @@ proc add_range_to_selection {w x y} { ###################################################################### ## -## config defaults - -set cursor_ptr arrow -font create font_diff -family Courier -size 10 -font create font_ui -catch { - label .dummy - eval font configure font_ui [font actual [.dummy cget -font]] - destroy .dummy -} - -font create font_uiitalic -font create font_uibold -font create font_diffbold -font create font_diffitalic - -foreach class {Button Checkbutton Entry Label - Labelframe Listbox Menu Message - Radiobutton Spinbox Text} { - option add *$class.font font_ui -} -unset class - -if {[is_Windows] || [is_MacOSX]} { - option add *Menu.tearOff 0 -} - -if {[is_MacOSX]} { - set M1B M1 - set M1T Cmd -} else { - set M1B Control - set M1T Ctrl -} - -proc apply_config {} { - global repo_config font_descs - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - if {[catch { - foreach {cn cv} $repo_config(gui.$name) { - font configure $font $cn $cv -weight normal - } - } err]} { - error_popup "Invalid font specified in gui.$name:\n\n$err" - } - foreach {cn cv} [font configure $font] { - font configure ${font}bold $cn $cv - font configure ${font}italic $cn $cv - } - font configure ${font}bold -weight bold - font configure ${font}italic -slant italic - } -} - -set default_config(merge.diffstat) true -set default_config(merge.summary) false -set default_config(merge.verbosity) 2 -set default_config(user.name) {} -set default_config(user.email) {} +## ui construction -set default_config(gui.matchtrackingbranch) false -set default_config(gui.pruneduringfetch) false -set default_config(gui.trustmtime) false -set default_config(gui.diffcontext) 5 -set default_config(gui.newbranchtemplate) {} -set default_config(gui.fontui) [font configure font_ui] -set default_config(gui.fontdiff) [font configure font_diff] -set font_descs { - {fontui font_ui {Main Font}} - {fontdiff font_diff {Diff/Console Font}} -} load_config 0 apply_config - -###################################################################### -## -## ui construction - set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 -.mbar add cascade -label Repository -menu .mbar.repository -.mbar add cascade -label Edit -menu .mbar.edit +.mbar add cascade -label [mc Repository] -menu .mbar.repository +.mbar add cascade -label [mc Edit] -menu .mbar.edit if {[is_enabled branch]} { - .mbar add cascade -label Branch -menu .mbar.branch + .mbar add cascade -label [mc Branch] -menu .mbar.branch } if {[is_enabled multicommit] || [is_enabled singlecommit]} { - .mbar add cascade -label Commit -menu .mbar.commit + .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit } if {[is_enabled transport]} { - .mbar add cascade -label Merge -menu .mbar.merge - .mbar add cascade -label Fetch -menu .mbar.fetch - .mbar add cascade -label Push -menu .mbar.push + .mbar add cascade -label [mc Merge] -menu .mbar.merge + .mbar add cascade -label [mc Remote] -menu .mbar.remote } . configure -menu .mbar @@ -1770,87 +1839,87 @@ if {[is_enabled transport]} { menu .mbar.repository .mbar.repository add command \ - -label {Browse Current Branch's Files} \ + -label [mc "Browse Current Branch's Files"] \ -command {browser::new $current_branch} set ui_browse_current [.mbar.repository index last] .mbar.repository add command \ - -label {Browse Branch Files...} \ + -label [mc "Browse Branch Files..."] \ -command browser_open::dialog .mbar.repository add separator .mbar.repository add command \ - -label {Visualize Current Branch's History} \ + -label [mc "Visualize Current Branch's History"] \ -command {do_gitk $current_branch} set ui_visualize_current [.mbar.repository index last] .mbar.repository add command \ - -label {Visualize All Branch History} \ + -label [mc "Visualize All Branch History"] \ -command {do_gitk --all} .mbar.repository add separator proc current_branch_write {args} { global current_branch .mbar.repository entryconf $::ui_browse_current \ - -label "Browse $current_branch's Files" + -label [mc "Browse %s's Files" $current_branch] .mbar.repository entryconf $::ui_visualize_current \ - -label "Visualize $current_branch's History" + -label [mc "Visualize %s's History" $current_branch] } trace add variable current_branch write current_branch_write if {[is_enabled multicommit]} { - .mbar.repository add command -label {Database Statistics} \ + .mbar.repository add command -label [mc "Database Statistics"] \ -command do_stats - .mbar.repository add command -label {Compress Database} \ + .mbar.repository add command -label [mc "Compress Database"] \ -command do_gc - .mbar.repository add command -label {Verify Database} \ + .mbar.repository add command -label [mc "Verify Database"] \ -command do_fsck_objects .mbar.repository add separator if {[is_Cygwin]} { .mbar.repository add command \ - -label {Create Desktop Icon} \ + -label [mc "Create Desktop Icon"] \ -command do_cygwin_shortcut } elseif {[is_Windows]} { .mbar.repository add command \ - -label {Create Desktop Icon} \ + -label [mc "Create Desktop Icon"] \ -command do_windows_shortcut } elseif {[is_MacOSX]} { .mbar.repository add command \ - -label {Create Desktop Icon} \ + -label [mc "Create Desktop Icon"] \ -command do_macosx_app } } -.mbar.repository add command -label Quit \ +.mbar.repository add command -label [mc Quit] \ -command do_quit \ -accelerator $M1T-Q # -- Edit Menu # menu .mbar.edit -.mbar.edit add command -label Undo \ +.mbar.edit add command -label [mc Undo] \ -command {catch {[focus] edit undo}} \ -accelerator $M1T-Z -.mbar.edit add command -label Redo \ +.mbar.edit add command -label [mc Redo] \ -command {catch {[focus] edit redo}} \ -accelerator $M1T-Y .mbar.edit add separator -.mbar.edit add command -label Cut \ +.mbar.edit add command -label [mc Cut] \ -command {catch {tk_textCut [focus]}} \ -accelerator $M1T-X -.mbar.edit add command -label Copy \ +.mbar.edit add command -label [mc Copy] \ -command {catch {tk_textCopy [focus]}} \ -accelerator $M1T-C -.mbar.edit add command -label Paste \ +.mbar.edit add command -label [mc Paste] \ -command {catch {tk_textPaste [focus]; [focus] see insert}} \ -accelerator $M1T-V -.mbar.edit add command -label Delete \ +.mbar.edit add command -label [mc Delete] \ -command {catch {[focus] delete sel.first sel.last}} \ -accelerator Del .mbar.edit add separator -.mbar.edit add command -label {Select All} \ +.mbar.edit add command -label [mc "Select All"] \ -command {catch {[focus] tag add sel 0.0 end}} \ -accelerator $M1T-A @@ -1859,29 +1928,29 @@ menu .mbar.edit if {[is_enabled branch]} { menu .mbar.branch - .mbar.branch add command -label {Create...} \ + .mbar.branch add command -label [mc "Create..."] \ -command branch_create::dialog \ -accelerator $M1T-N lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] - .mbar.branch add command -label {Checkout...} \ + .mbar.branch add command -label [mc "Checkout..."] \ -command branch_checkout::dialog \ -accelerator $M1T-O lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] - .mbar.branch add command -label {Rename...} \ + .mbar.branch add command -label [mc "Rename..."] \ -command branch_rename::dialog lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] - .mbar.branch add command -label {Delete...} \ + .mbar.branch add command -label [mc "Delete..."] \ -command branch_delete::dialog lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] - .mbar.branch add command -label {Reset...} \ + .mbar.branch add command -label [mc "Reset..."] \ -command merge::reset_hard lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] @@ -1893,7 +1962,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { menu .mbar.commit .mbar.commit add radiobutton \ - -label {New Commit} \ + -label [mc "New Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value new @@ -1901,7 +1970,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add radiobutton \ - -label {Amend Last Commit} \ + -label [mc "Amend Last Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value amend @@ -1910,40 +1979,41 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar.commit add separator - .mbar.commit add command -label Rescan \ + .mbar.commit add command -label [mc Rescan] \ -command do_rescan \ -accelerator F5 lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Stage To Commit} \ - -command do_add_selection + .mbar.commit add command -label [mc "Stage To Commit"] \ + -command do_add_selection \ + -accelerator $M1T-T lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Stage Changed Files To Commit} \ + .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \ -command do_add_all \ -accelerator $M1T-I lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Unstage From Commit} \ + .mbar.commit add command -label [mc "Unstage From Commit"] \ -command do_unstage_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Revert Changes} \ + .mbar.commit add command -label [mc "Revert Changes"] \ -command do_revert_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add separator - .mbar.commit add command -label {Sign Off} \ + .mbar.commit add command -label [mc "Sign Off"] \ -command do_signoff \ -accelerator $M1T-S - .mbar.commit add command -label Commit \ + .mbar.commit add command -label [mc Commit@@verb] \ -command do_commit \ -accelerator $M1T-Return lappend disable_on_lock \ @@ -1954,12 +2024,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { # if {[is_enabled branch]} { menu .mbar.merge - .mbar.merge add command -label {Local Merge...} \ + .mbar.merge add command -label [mc "Local Merge..."] \ -command merge::dialog \ -accelerator $M1T-M lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] - .mbar.merge add command -label {Abort Merge...} \ + .mbar.merge add command -label [mc "Abort Merge..."] \ -command merge::reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] @@ -1968,41 +2038,46 @@ if {[is_enabled branch]} { # -- Transport Menu # if {[is_enabled transport]} { - menu .mbar.fetch + menu .mbar.remote - menu .mbar.push - .mbar.push add command -label {Push...} \ + .mbar.remote add command \ + -label [mc "Push..."] \ -command do_push_anywhere \ -accelerator $M1T-P - .mbar.push add command -label {Delete...} \ + .mbar.remote add command \ + -label [mc "Delete..."] \ -command remote_branch_delete::dialog } if {[is_MacOSX]} { # -- Apple Menu (Mac OS X only) # - .mbar add cascade -label Apple -menu .mbar.apple + .mbar add cascade -label [mc Apple] -menu .mbar.apple menu .mbar.apple - .mbar.apple add command -label "About [appname]" \ + .mbar.apple add command -label [mc "About %s" [appname]] \ -command do_about - .mbar.apple add command -label "Options..." \ - -command do_options + .mbar.apple add separator + .mbar.apple add command \ + -label [mc "Preferences..."] \ + -command do_options \ + -accelerator $M1T-, + bind . <$M1B-,> do_options } else { # -- Edit Menu # .mbar.edit add separator - .mbar.edit add command -label {Options...} \ + .mbar.edit add command -label [mc "Options..."] \ -command do_options } # -- Help Menu # -.mbar add cascade -label Help -menu .mbar.help +.mbar add cascade -label [mc Help] -menu .mbar.help menu .mbar.help if {![is_MacOSX]} { - .mbar.help add command -label "About [appname]" \ + .mbar.help add command -label [mc "About %s" [appname]] \ -command do_about } @@ -2039,17 +2114,11 @@ if {[file isfile $doc_path]} { } if {$browser ne {}} { - .mbar.help add command -label {Online Documentation} \ + .mbar.help add command -label [mc "Online Documentation"] \ -command [list exec $browser $doc_url &] } unset browser doc_path doc_url -set root_exists 0 -bind . <Visibility> { - bind . <Visibility> {} - set root_exists 1 -} - # -- Standard bindings # wm protocol . WM_DELETE_WINDOW do_quit @@ -2129,7 +2198,7 @@ blame { } blame { if {$head eq {} && ![file exists $path]} { - puts stderr "fatal: cannot stat path $path: No such file or directory" + puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path] exit 1 } blame::new $head $path @@ -2141,7 +2210,8 @@ citool - gui { if {[llength $argv] != 0} { puts -nonewline stderr "usage: $argv0" - if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} { + if {$subcommand ne {gui} + && [file tail $argv0] ne "git-$subcommand"} { puts -nonewline stderr " $subcommand" } puts stderr {} @@ -2161,7 +2231,7 @@ frame .branch \ -borderwidth 1 \ -relief sunken label .branch.l1 \ - -text {Current Branch:} \ + -text [mc "Current Branch:"] \ -anchor w \ -justify left label .branch.cb \ @@ -2174,15 +2244,15 @@ pack .branch -side top -fill x # -- Main Window Layout # -panedwindow .vpane -orient vertical -panedwindow .vpane.files -orient horizontal +panedwindow .vpane -orient horizontal +panedwindow .vpane.files -orient vertical .vpane add .vpane.files -sticky nsew -height 100 -width 200 pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # frame .vpane.files.index -height 100 -width 200 -label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \ +label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \ -background lightgreen text $ui_index -background white -borderwidth 0 \ -width 20 -height 10 \ @@ -2197,12 +2267,11 @@ pack .vpane.files.index.title -side top -fill x pack .vpane.files.index.sx -side bottom -fill x pack .vpane.files.index.sy -side right -fill y pack $ui_index -side left -fill both -expand 1 -.vpane.files add .vpane.files.index -sticky nsew # -- Working Directory File List # frame .vpane.files.workdir -height 100 -width 200 -label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \ +label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ -background lightsalmon text $ui_workdir -background white -borderwidth 0 \ -width 20 -height 10 \ @@ -2217,7 +2286,9 @@ pack .vpane.files.workdir.title -side top -fill x pack .vpane.files.workdir.sx -side bottom -fill x pack .vpane.files.workdir.sy -side right -fill y pack $ui_workdir -side left -fill both -expand 1 + .vpane.files add .vpane.files.workdir -sticky nsew +.vpane.files add .vpane.files.index -sticky nsew foreach i [list $ui_index $ui_workdir] { rmsel_tag $i @@ -2230,8 +2301,8 @@ unset i frame .vpane.lower -height 300 -width 400 frame .vpane.lower.commarea frame .vpane.lower.diff -relief sunken -borderwidth 1 -pack .vpane.lower.commarea -side top -fill x -pack .vpane.lower.diff -side bottom -fill both -expand 1 +pack .vpane.lower.diff -fill both -expand 1 +pack .vpane.lower.commarea -side bottom -fill x .vpane add .vpane.lower -sticky nsew # -- Commit Area Buttons @@ -2243,29 +2314,29 @@ label .vpane.lower.commarea.buttons.l -text {} \ pack .vpane.lower.commarea.buttons.l -side top -fill x pack .vpane.lower.commarea.buttons -side left -fill y -button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ +button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ -command do_rescan pack .vpane.lower.commarea.buttons.rescan -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} -button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \ +button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.incall conf -state} -button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ +button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ -command do_signoff pack .vpane.lower.commarea.buttons.signoff -side top -fill x -button .vpane.lower.commarea.buttons.commit -text {Commit} \ +button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \ -command do_commit pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} -button .vpane.lower.commarea.buttons.push -text {Push} \ +button .vpane.lower.commarea.buttons.push -text [mc Push] \ -command do_push_anywhere pack .vpane.lower.commarea.buttons.push -side top -fill x @@ -2276,14 +2347,14 @@ frame .vpane.lower.commarea.buffer.header set ui_comm .vpane.lower.commarea.buffer.t set ui_coml .vpane.lower.commarea.buffer.header.l radiobutton .vpane.lower.commarea.buffer.header.new \ - -text {New Commit} \ + -text [mc "New Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value new lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.new conf -state] radiobutton .vpane.lower.commarea.buffer.header.amend \ - -text {Amend Last Commit} \ + -text [mc "Amend Last Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value amend @@ -2295,12 +2366,12 @@ label $ui_coml \ proc trace_commit_type {varname args} { global ui_coml commit_type switch -glob -- $commit_type { - initial {set txt {Initial Commit Message:}} - amend {set txt {Amended Commit Message:}} - amend-initial {set txt {Amended Initial Commit Message:}} - amend-merge {set txt {Amended Merge Commit Message:}} - merge {set txt {Merge Commit Message:}} - * {set txt {Commit Message:}} + initial {set txt [mc "Initial Commit Message:"]} + amend {set txt [mc "Amended Commit Message:"]} + amend-initial {set txt [mc "Amended Initial Commit Message:"]} + amend-merge {set txt [mc "Amended Merge Commit Message:"]} + merge {set txt [mc "Merge Commit Message:"]} + * {set txt [mc "Commit Message:"]} } $ui_coml conf -text $txt } @@ -2329,23 +2400,23 @@ pack .vpane.lower.commarea.buffer -side left -fill y set ctxm .vpane.lower.commarea.buffer.ctxm menu $ctxm -tearoff 0 $ctxm add command \ - -label {Cut} \ + -label [mc Cut] \ -command {tk_textCut $ui_comm} $ctxm add command \ - -label {Copy} \ + -label [mc Copy] \ -command {tk_textCopy $ui_comm} $ctxm add command \ - -label {Paste} \ + -label [mc Paste] \ -command {tk_textPaste $ui_comm} $ctxm add command \ - -label {Delete} \ + -label [mc Delete] \ -command {$ui_comm delete sel.first sel.last} $ctxm add separator $ctxm add command \ - -label {Select All} \ + -label [mc "Select All"] \ -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} $ctxm add command \ - -label {Copy All} \ + -label [mc "Copy All"] \ -command { $ui_comm tag add sel 0.0 end tk_textCopy $ui_comm @@ -2353,7 +2424,7 @@ $ctxm add command \ } $ctxm add separator $ctxm add command \ - -label {Sign Off} \ + -label [mc "Sign Off"] \ -command do_signoff bind_button3 $ui_comm "tk_popup $ctxm %X %Y" @@ -2369,7 +2440,7 @@ proc trace_current_diff_path {varname args} { } else { set p $current_diff_path set s [mapdesc [lindex $file_states($p) 0] $p] - set f {File:} + set f [mc "File:"] set p [escape_path $p] set o normal } @@ -2403,7 +2474,7 @@ pack .vpane.lower.diff.header.path -fill x set ctxm .vpane.lower.diff.header.ctxm menu $ctxm -tearoff 0 $ctxm add command \ - -label {Copy} \ + -label [mc Copy] \ -command { clipboard clear clipboard append \ @@ -2471,19 +2542,19 @@ $ui_diff tag raise sel set ctxm .vpane.lower.diff.body.ctxm menu $ctxm -tearoff 0 $ctxm add command \ - -label {Refresh} \ + -label [mc Refresh] \ -command reshow_diff lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ - -label {Copy} \ + -label [mc Copy] \ -command {tk_textCopy $ui_diff} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ - -label {Select All} \ + -label [mc "Select All"] \ -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ - -label {Copy All} \ + -label [mc "Copy All"] \ -command { $ui_diff tag add sel 0.0 end tk_textCopy $ui_diff @@ -2492,45 +2563,45 @@ $ctxm add command \ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ - -label {Apply/Reverse Hunk} \ + -label [mc "Apply/Reverse Hunk"] \ -command {apply_hunk $cursorX $cursorY} set ui_diff_applyhunk [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] $ctxm add separator $ctxm add command \ - -label {Decrease Font Size} \ + -label [mc "Decrease Font Size"] \ -command {incr_font_size font_diff -1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ - -label {Increase Font Size} \ + -label [mc "Increase Font Size"] \ -command {incr_font_size font_diff 1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ - -label {Show Less Context} \ + -label [mc "Show Less Context"] \ -command {if {$repo_config(gui.diffcontext) >= 1} { incr repo_config(gui.diffcontext) -1 reshow_diff }} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ - -label {Show More Context} \ + -label [mc "Show More Context"] \ -command {if {$repo_config(gui.diffcontext) < 99} { incr repo_config(gui.diffcontext) reshow_diff }} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator -$ctxm add command -label {Options...} \ +$ctxm add command -label [mc "Options..."] \ -command do_options proc popup_diff_menu {ctxm x y X Y} { global current_diff_path file_states set ::cursorX $x set ::cursorY $y if {$::ui_index eq $::current_diff_side} { - set l "Unstage Hunk From Commit" + set l [mc "Unstage Hunk From Commit"] } else { - set l "Stage Hunk For Commit" + set l [mc "Stage Hunk For Commit"] } if {$::is_3way_diff || $current_diff_path eq {} @@ -2549,7 +2620,7 @@ bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y] # set main_status [::status_bar::new .status] pack .status -anchor w -side bottom -fill x -$main_status show {Initializing...} +$main_status show [mc "Initializing..."] # -- Load geometry # @@ -2557,17 +2628,19 @@ catch { set gm $repo_config(gui.geometry) wm geometry . [lindex $gm 0] .vpane sash place 0 \ - [lindex [.vpane sash coord 0] 0] \ - [lindex $gm 1] + [lindex $gm 1] \ + [lindex [.vpane sash coord 0] 1] .vpane.files sash place 0 \ - [lindex $gm 2] \ - [lindex [.vpane.files sash coord 0] 1] + [lindex [.vpane.files sash coord 0] 0] \ + [lindex $gm 2] unset gm } # -- Key Bindings # bind $ui_comm <$M1B-Key-Return> {do_commit;break} +bind $ui_comm <$M1B-Key-t> {do_add_selection;break} +bind $ui_comm <$M1B-Key-T> {do_add_selection;break} bind $ui_comm <$M1B-Key-i> {do_add_all;break} bind $ui_comm <$M1B-Key-I> {do_add_all;break} bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} @@ -2617,6 +2690,8 @@ bind . <$M1B-Key-r> do_rescan bind . <$M1B-Key-R> do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff +bind . <$M1B-Key-t> do_add_selection +bind . <$M1B-Key-T> do_add_selection bind . <$M1B-Key-i> do_add_all bind . <$M1B-Key-I> do_add_all bind . <$M1B-Key-Return> do_commit @@ -2640,13 +2715,13 @@ focus -force $ui_comm if {[is_Cygwin]} { set ignored_env 0 set suggest_user {} - set msg "Possible environment issues exist. + set msg [mc "Possible environment issues exist. The following environment variables are probably going to be ignored by any Git subprocess run -by [appname]: +by %s: -" +" [appname]] foreach name [array names env] { switch -regexp -- $name { {^GIT_INDEX_FILE$} - @@ -2670,18 +2745,18 @@ by [appname]: } } if {$ignored_env > 0} { - append msg " + append msg [mc " This is due to a known issue with the -Tcl binary distributed by Cygwin." +Tcl binary distributed by Cygwin."] if {$suggest_user ne {}} { - append msg " + append msg [mc " -A good replacement for $suggest_user +A good replacement for %s is placing values for the user.name and user.email settings into your personal ~/.gitconfig file. -" +" $suggest_user] } warn_popup $msg } @@ -2693,8 +2768,14 @@ user.email settings into your personal if {[is_enabled transport]} { load_all_remotes - populate_fetch_menu + set n [.mbar.remote index end] populate_push_menu + populate_fetch_menu + set n [expr {[.mbar.remote index end] - $n}] + if {$n > 0} { + .mbar.remote insert $n separator + } + unset n } if {[winfo exists $ui_comm]} { diff --git a/git-gui/lib/about.tcl b/git-gui/lib/about.tcl new file mode 100644 index 0000000000..719fc547b3 --- /dev/null +++ b/git-gui/lib/about.tcl @@ -0,0 +1,81 @@ +# git-gui about git-gui dialog +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_about {} { + global appvers copyright oguilib + global tcl_patchLevel tk_patchLevel + + set w .about_dialog + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10 + label $w.header -text [mc "About %s" [appname]] \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.close -text {Close} \ + -default active \ + -command [list destroy $w] + pack $w.buttons.close -side right + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + label $w.desc \ + -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright" \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.desc -side top -fill x -padx 5 -pady 5 + + set v {} + append v "git-gui version $appvers\n" + append v "[git version]\n" + append v "\n" + if {$tcl_patchLevel eq $tk_patchLevel} { + append v "Tcl/Tk version $tcl_patchLevel" + } else { + append v "Tcl version $tcl_patchLevel" + append v ", Tk version $tk_patchLevel" + } + + set d {} + append d "git wrapper: $::_git\n" + append d "git exec dir: [gitexec]\n" + append d "git-gui lib: $oguilib" + + label $w.vers \ + -text $v \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.vers -side top -fill x -padx 5 -pady 5 + + label $w.dirs \ + -text $d \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.dirs -side top -fill x -padx 5 -pady 5 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command \ + -label {Copy} \ + -command " + clipboard clear + clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] + " + + bind $w <Visibility> "grab $w; focus $w.buttons.close" + bind $w <Key-Escape> "destroy $w" + bind $w <Key-Return> "destroy $w" + bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" + wm title $w "About [appname]" + tkwait window $w +} diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 96072847a2..00ecf21333 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -74,11 +74,11 @@ constructor new {i_commit i_path} { set path $i_path make_toplevel top w - wm title $top "[appname] ([reponame]): File Viewer" + wm title $top [append "[appname] ([reponame]): " [mc "File Viewer"]] frame $w.header -background gold label $w.header.commit_l \ - -text {Commit:} \ + -text [mc "Commit:"] \ -background gold \ -anchor w \ -justify left @@ -101,7 +101,7 @@ constructor new {i_commit i_path} { -anchor w \ -justify left label $w.header.path_l \ - -text {File:} \ + -text [mc "File:"] \ -background gold \ -anchor w \ -justify left @@ -246,7 +246,7 @@ constructor new {i_commit i_path} { menu $w.ctxm -tearoff 0 $w.ctxm add command \ - -label "Copy Commit" \ + -label [mc "Copy Commit"] \ -command [cb _copycommit] foreach i $w_columns { @@ -366,7 +366,7 @@ method _load {jump} { set amov_data [list [list]] set asim_data [list [list]] - $status show "Reading $commit:[escape_path $path]..." + $status show [mc "Reading %s..." "$commit:[escape_path $path]"] $w_path conf -text [escape_path $path] if {$commit eq {}} { set fd [open $path r] @@ -470,7 +470,7 @@ method _read_file {fd jump} { _exec_blame $this $w_asim @asim_data \ [list] \ - { copy/move tracking} + [mc "Loading copy/move tracking annotations..."] } } ifdeleted { catch {close $fd} } @@ -489,8 +489,8 @@ method _exec_blame {cur_w cur_d options cur_s} { set blame_lines 0 $status start \ - "Loading$cur_s annotations..." \ - {lines annotated} + $cur_s \ + [mc "lines annotated"] } method _read_blame {fd cur_w cur_d} { @@ -671,10 +671,10 @@ method _read_blame {fd cur_w cur_d} { if {$cur_w eq $w_asim} { _exec_blame $this $w_amov @amov_data \ $original_options \ - { original location} + [mc "Loading original location annotations..."] } else { set current_fd {} - $status stop {Annotation complete.} + $status stop [mc "Annotation complete."] } } else { $status update $blame_lines $total_lines @@ -728,7 +728,7 @@ method _showcommit {cur_w lno} { if {$dat eq {}} { set cmit {} - $w_cviewer insert end "Loading annotation..." still_loading + $w_cviewer insert end [mc "Loading annotation..."] still_loading } else { set cmit [lindex $dat 0] set file [lindex $dat 1] @@ -743,20 +743,14 @@ method _showcommit {cur_w lno} { set author_time {} catch {set author_name $header($cmit,author)} catch {set author_email $header($cmit,author-mail)} - catch {set author_time [clock format \ - $header($cmit,author-time) \ - -format {%Y-%m-%d %H:%M:%S} - ]} + catch {set author_time [format_date $header($cmit,author-time)]} set committer_name {} set committer_email {} set committer_time {} catch {set committer_name $header($cmit,committer)} catch {set committer_email $header($cmit,committer-mail)} - catch {set committer_time [clock format \ - $header($cmit,committer-time) \ - -format {%Y-%m-%d %H:%M:%S} - ]} + catch {set committer_time [format_date $header($cmit,committer-time)]} if {[catch {set msg $header($cmit,message)}]} { set msg {} @@ -790,16 +784,16 @@ method _showcommit {cur_w lno} { } $w_cviewer insert end "commit $cmit\n" header_key - $w_cviewer insert end "Author:\t" header_key + $w_cviewer insert end [strcat [mc "Author:"] "\t"] header_key $w_cviewer insert end "$author_name $author_email" header_val $w_cviewer insert end " $author_time\n" header_val - $w_cviewer insert end "Committer:\t" header_key + $w_cviewer insert end [strcat [mc "Committer:"] "\t"] header_key $w_cviewer insert end "$committer_name $committer_email" header_val $w_cviewer insert end " $committer_time\n" header_val if {$file ne $path} { - $w_cviewer insert end "Original File:\t" header_key + $w_cviewer insert end [strcat [mc "Original File:"] "\t"] header_key $w_cviewer insert end "[escape_path $file]\n" header_val } @@ -892,10 +886,7 @@ method _open_tooltip {cur_w} { set author_time {} catch {set author_name $header($cmit,author)} catch {set summary $header($cmit,summary)} - catch {set author_time [clock format \ - $header($cmit,author-time) \ - -format {%Y-%m-%d %H:%M:%S} - ]} + catch {set author_time [format_date $header($cmit,author-time)]} $tooltip_t insert end "commit $cmit\n" $tooltip_t insert end "$author_name $author_time\n" @@ -914,23 +905,20 @@ method _open_tooltip {cur_w} { set author_time {} catch {set author_name $header($cmit,author)} catch {set summary $header($cmit,summary)} - catch {set author_time [clock format \ - $header($cmit,author-time) \ - -format {%Y-%m-%d %H:%M:%S} - ]} + catch {set author_time [format_date $header($cmit,author-time)]} - $tooltip_t insert end "Originally By:\n" section_header + $tooltip_t insert end [strcat [mc "Originally By:"] "\n"] section_header $tooltip_t insert end "commit $cmit\n" $tooltip_t insert end "$author_name $author_time\n" $tooltip_t insert end "$summary\n" if {$file ne $path} { - $tooltip_t insert end "In File: " section_header + $tooltip_t insert end [strcat [mc "In File:"] " "] section_header $tooltip_t insert end "$file\n" } $tooltip_t insert end "\n" - $tooltip_t insert end "Copied Or Moved Here By:\n" section_header + $tooltip_t insert end [strcat [mc "Copied Or Moved Here By:"] "\n"] section_header $tooltip_t insert end $save } diff --git a/git-gui/lib/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl index 72c45b4554..6603703ea1 100644 --- a/git-gui/lib/branch_checkout.tcl +++ b/git-gui/lib/branch_checkout.tcl @@ -11,37 +11,37 @@ field opt_detach 0; # force a detached head case? constructor dialog {} { make_toplevel top w - wm title $top "[appname] ([reponame]): Checkout Branch" + wm title $top [append "[appname] ([reponame]): " [mc "Checkout Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text {Checkout Branch} -font font_uibold + label $w.header -text [mc "Checkout Branch"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.create -text Checkout \ + button $w.buttons.create -text [mc Checkout] \ -default active \ -command [cb _checkout] pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ + 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 - set w_rev [::choose_rev::new $w.rev {Revision}] + set w_rev [::choose_rev::new $w.rev [mc Revision]] $w_rev bind_listbox <Double-Button-1> [cb _checkout] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.options -text {Options} + labelframe $w.options -text [mc Options] checkbutton $w.options.fetch \ - -text {Fetch Tracking Branch} \ + -text [mc "Fetch Tracking Branch"] \ -variable @opt_fetch pack $w.options.fetch -anchor nw checkbutton $w.options.detach \ - -text {Detach From Local Branch} \ + -text [mc "Detach From Local Branch"] \ -variable @opt_detach pack $w.options.detach -anchor nw diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl index def615d19d..53dfb4ce6b 100644 --- a/git-gui/lib/branch_create.tcl +++ b/git-gui/lib/branch_create.tcl @@ -19,28 +19,28 @@ constructor dialog {} { global repo_config make_toplevel top w - wm title $top "[appname] ([reponame]): Create Branch" + wm title $top [append "[appname] ([reponame]): " [mc "Create Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text {Create New Branch} -font font_uibold + label $w.header -text [mc "Create New Branch"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.create -text Create \ + button $w.buttons.create -text [mc Create] \ -default active \ -command [cb _create] pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ + 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 {Branch Name} + labelframe $w.desc -text [mc "Branch Name"] radiobutton $w.desc.name_r \ -anchor w \ - -text {Name:} \ + -text [mc "Name:"] \ -value user \ -variable @name_type set w_name $w.desc.name_t @@ -55,7 +55,7 @@ constructor dialog {} { radiobutton $w.desc.match_r \ -anchor w \ - -text {Match Tracking Branch Name} \ + -text [mc "Match Tracking Branch Name"] \ -value match \ -variable @name_type grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2 @@ -63,38 +63,38 @@ constructor dialog {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - set w_rev [::choose_rev::new $w.rev {Starting Revision}] + set w_rev [::choose_rev::new $w.rev [mc "Starting Revision"]] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.options -text {Options} + labelframe $w.options -text [mc Options] frame $w.options.merge - label $w.options.merge.l -text {Update Existing Branch:} + label $w.options.merge.l -text [mc "Update Existing Branch:"] pack $w.options.merge.l -side left radiobutton $w.options.merge.no \ - -text No \ + -text [mc No] \ -value none \ -variable @opt_merge pack $w.options.merge.no -side left radiobutton $w.options.merge.ff \ - -text {Fast Forward Only} \ + -text [mc "Fast Forward Only"] \ -value ff \ -variable @opt_merge pack $w.options.merge.ff -side left radiobutton $w.options.merge.reset \ - -text {Reset} \ + -text [mc Reset] \ -value reset \ -variable @opt_merge pack $w.options.merge.reset -side left pack $w.options.merge -anchor nw checkbutton $w.options.fetch \ - -text {Fetch Tracking Branch} \ + -text [mc "Fetch Tracking Branch"] \ -variable @opt_fetch pack $w.options.fetch -anchor nw checkbutton $w.options.checkout \ - -text {Checkout After Creation} \ + -text [mc "Checkout After Creation"] \ -variable @opt_checkout pack $w.options.checkout -anchor nw pack $w.options -anchor nw -fill x -pady 5 -padx 5 @@ -128,7 +128,7 @@ method _create {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Please select a tracking branch." + -message [mc "Please select a tracking branch."] return } if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} { @@ -137,7 +137,7 @@ method _create {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Tracking branch [$w get] is not a branch in the remote repository." + -message [mc "Tracking branch %s is not a branch in the remote repository." [$w get]] return } } @@ -150,7 +150,7 @@ method _create {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Please supply a branch name." + -message [mc "Please supply a branch name."] focus $w_name return } @@ -161,7 +161,7 @@ method _create {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "'$newbranch' is not an acceptable branch name." + -message [mc "'%s' is not an acceptable branch name." $newbranch] focus $w_name return } diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl index c7573c6c72..86c4f73370 100644 --- a/git-gui/lib/branch_delete.tcl +++ b/git-gui/lib/branch_delete.tcl @@ -12,29 +12,29 @@ constructor dialog {} { global current_branch make_toplevel top w - wm title $top "[appname] ([reponame]): Delete Branch" + wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text {Delete Local Branch} -font font_uibold + label $w.header -text [mc "Delete Local Branch"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons set w_delete $w.buttons.delete button $w_delete \ - -text Delete \ + -text [mc Delete] \ -default active \ -state disabled \ -command [cb _delete] pack $w_delete -side right button $w.buttons.cancel \ - -text {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.list -text {Local Branches} + labelframe $w.list -text [mc "Local Branches"] set w_heads $w.list.l listbox $w_heads \ -height 10 \ @@ -49,9 +49,9 @@ constructor dialog {} { set w_check [choose_rev::new \ $w.check \ - {Delete Only If Merged Into} \ + [mc "Delete Only If Merged Into"] \ ] - $w_check none {Always (Do not perform merge test.)} + $w_check none [mc "Always (Do not perform merge test.)"] pack $w.check -anchor nw -fill x -pady 5 -padx 5 foreach h [load_all_heads] { @@ -100,7 +100,7 @@ method _delete {} { lappend to_delete [list $b $o] } if {$not_merged ne {}} { - set msg "The following branches are not completely merged into [$w_check get]: + set msg "[mc "The following branches are not completely merged into %s:" [$w_check get]] - [join $not_merged "\n - "]" tk_messageBox \ @@ -112,9 +112,7 @@ method _delete {} { } if {$to_delete eq {}} return if {$check_cmt eq {}} { - set msg {Recovering deleted branches is difficult. - -Delete the selected branches?} + set msg [mc "Recovering deleted branches is difficult. \n\n Delete the selected branches?"] if {[tk_messageBox \ -icon warning \ -type yesno \ @@ -140,7 +138,7 @@ Delete the selected branches?} -type ok \ -title [wm title $w] \ -parent $w \ - -message "Failed to delete branches:\n$failed" + -message [mc "Failed to delete branches:\n%s" $failed] } destroy $w diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 1cadc31d20..166538808f 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -11,7 +11,7 @@ constructor dialog {} { global current_branch make_toplevel top w - wm title $top "[appname] ([reponame]): Rename Branch" + wm title $top [append "[appname] ([reponame]): " [mc "Rename Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } @@ -19,24 +19,24 @@ constructor dialog {} { set oldname $current_branch set newname [get_config gui.newbranchtemplate] - label $w.header -text {Rename Branch} -font font_uibold + label $w.header -text [mc "Rename Branch"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.rename -text Rename \ + button $w.buttons.rename -text [mc Rename] \ -default active \ -command [cb _rename] pack $w.buttons.rename -side right - button $w.buttons.cancel -text {Cancel} \ + 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 frame $w.rename - label $w.rename.oldname_l -text {Branch:} + label $w.rename.oldname_l -text [mc "Branch:"] eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads] - label $w.rename.newname_l -text {New Name:} + label $w.rename.newname_l -text [mc "New Name:"] entry $w.rename.newname_t \ -borderwidth 1 \ -relief sunken \ @@ -72,7 +72,7 @@ method _rename {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Please select a branch to rename." + -message [mc "Please select a branch to rename."] focus $w.rename.oldname_m return } @@ -83,7 +83,7 @@ method _rename {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Please supply a branch name." + -message [mc "Please supply a branch name."] focus $w.rename.newname_t return } @@ -93,7 +93,7 @@ method _rename {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Branch '$newname' already exists." + -message [mc "Branch '%s' already exists." $newname] focus $w.rename.newname_t return } @@ -103,7 +103,7 @@ method _rename {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "We do not like '$newname' as a branch name." + -message [mc "'%s' is not an acceptable branch name." $newname] focus $w.rename.newname_t return } @@ -114,7 +114,7 @@ method _rename {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "Failed to rename '$oldname'.\n\n$err" + -message [strcat [mc "Failed to rename '%s'." $oldname] "\n\n$err"] return } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 31349009ae..53d5a62816 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -14,7 +14,7 @@ field w field browser_commit field browser_path field browser_files {} -field browser_status {Starting...} +field browser_status [mc "Starting..."] field browser_stack {} field browser_busy 1 @@ -23,7 +23,7 @@ field ls_buf {}; # Buffered record output from ls-tree constructor new {commit {path {}}} { global cursor_ptr M1B make_toplevel top w - wm title $top "[appname] ([reponame]): File Browser" + wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]] set browser_commit $commit set browser_path $browser_commit:$path @@ -122,7 +122,7 @@ method _parent {} { } else { regsub {/[^/]+$} $browser_path {} browser_path } - set browser_status "Loading $browser_path..." + set browser_status [mc "Loading %s..." $browser_path] _ls $this [lindex $parent 0] [lindex $parent 1] } } @@ -139,7 +139,7 @@ method _enter {} { tree { set name [lindex $info 2] set escn [escape_path $name] - set browser_status "Loading $escn..." + set browser_status [mc "Loading %s..." $escn] append browser_path $escn _ls $this [lindex $info 1] $name } @@ -183,7 +183,7 @@ method _ls {tree_id {name {}}} { -align center -padx 5 -pady 1 \ -name icon0 \ -image ::browser::img_parent - $w insert end {[Up To Parent]} + $w insert end [mc "\[Up To Parent\]"] lappend browser_files parent } lappend browser_stack [list $tree_id $name] @@ -242,7 +242,7 @@ method _read {fd} { if {[eof $fd]} { close $fd - set browser_status Ready. + set browser_status [mc "Ready."] set browser_busy 0 set ls_buf {} if {$n > 0} { @@ -263,27 +263,27 @@ field w_rev ; # mega-widget to pick the initial revision constructor dialog {} { make_toplevel top w - wm title $top "[appname] ([reponame]): Browse Branch Files" + wm title $top [append "[appname] ([reponame]): " [mc "Browse Branch Files"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } label $w.header \ - -text {Browse Branch Files} \ + -text [mc "Browse Branch Files"] \ -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.browse -text Browse \ + button $w.buttons.browse -text [mc Browse] \ -default active \ -command [cb _open] pack $w.buttons.browse -side right - button $w.buttons.cancel -text {Cancel} \ + 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 - set w_rev [::choose_rev::new $w.rev {Revision}] + set w_rev [::choose_rev::new $w.rev [mc Revision]] $w_rev bind_listbox <Double-Button-1> [cb _open] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl index 76f04f2854..f243966924 100644 --- a/git-gui/lib/checkout_op.tcl +++ b/git-gui/lib/checkout_op.tcl @@ -76,7 +76,7 @@ method run {} { _toplevel $this {Refreshing Tracking Branch} set w_cons [::console::embed \ $w.console \ - "Fetching $r_name from $remote"] + [mc "Fetching %s from %s" $r_name $remote]] pack $w.console -fill both -expand 1 $w_cons exec $cmd [cb _finish_fetch] @@ -124,7 +124,7 @@ method _finish_fetch {ok} { } if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} { set ok 0 - $w_cons insert "fatal: Cannot resolve $l_trck" + $w_cons insert [mc "fatal: Cannot resolve %s" $l_trck] $w_cons insert $err } } @@ -137,7 +137,7 @@ method _finish_fetch {ok} { destroy $w set w {} } else { - button $w.close -text Close -command [list destroy $w] + button $w.close -text [mc Close] -command [list destroy $w] pack $w.close -side bottom -anchor e -padx 10 -pady 10 } @@ -166,7 +166,7 @@ method _update_ref {} { # Assume it does not exist, and that is what the error was. # if {!$create} { - _error $this "Branch '$newbranch' does not exist." + _error $this [mc "Branch '%s' does not exist." $newbranch] return 0 } @@ -176,7 +176,7 @@ method _update_ref {} { # We were told to create it, but not do a merge. # Bad. Name shouldn't have existed. # - _error $this "Branch '$newbranch' already exists." + _error $this [mc "Branch '%s' already exists." $newbranch] return 0 } elseif {!$create && $merge_type eq {none}} { # We aren't creating, it exists and we don't merge. @@ -203,7 +203,7 @@ method _update_ref {} { set new $cur set new_hash $cur } else { - _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required." + _error $this [mc "Branch '%s' already exists.\n\nIt cannot fast-forward to %s.\nA merge is required." $newbranch $new_expr] return 0 } } @@ -217,7 +217,7 @@ method _update_ref {} { } } default { - _error $this "Merge strategy '$merge_type' not supported." + _error $this [mc "Merge strategy '%s' not supported." $merge_type] return 0 } } @@ -236,7 +236,7 @@ method _update_ref {} { if {[catch { git update-ref -m $reflog_msg $ref $new $cur } err]} { - _error $this "Failed to update '$newbranch'.\n\n$err" + _error $this [strcat [mc "Failed to update '%s'." $newbranch] "\n\n$err"] return 0 } } @@ -248,7 +248,7 @@ method _checkout {} { if {[lock_index checkout_op]} { after idle [cb _start_checkout] } else { - _error $this "Staging area (index) is already locked." + _error $this [mc "Staging area (index) is already locked."] delete_this } } @@ -263,12 +263,12 @@ method _start_checkout {} { && $curType eq {normal} && $curHEAD eq $HEAD} { } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. + info_popup [mc "Last scanned state does not match repository state. Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. The rescan will be automatically started now. -} +"] unlock_index rescan ui_ready delete_this @@ -319,7 +319,7 @@ method _readtree {} { set readtree_d {} $::main_status start \ - "Updating working directory to '[_name $this]'..." \ + [mc "Updating working directory to '%s'..." [_name $this]] \ {files checked out} set fd [git_read --stderr read-tree \ @@ -350,12 +350,12 @@ method _readtree_wait {fd} { if {[catch {close $fd}]} { set err $readtree_d regsub {^fatal: } $err {} err - $::main_status stop "Aborted checkout of '[_name $this]' (file level merging is required)." - warn_popup "File level merge required. + $::main_status stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]] + warn_popup [strcat [mc "File level merge required."] " $err -Staying on branch '$current_branch'." +" [mc "Staying on branch '%s'." $current_branch]] unlock_index delete_this return @@ -426,9 +426,9 @@ method _after_readtree {} { } if {$is_detached} { - info_popup "You are no longer on a local branch. + info_popup [mc "You are no longer on a local branch. -If you wanted to be on a branch, create one now starting from 'This Detached Checkout'." +If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."] } # -- Update our repository state. If we were previously in @@ -443,7 +443,7 @@ If you wanted to be on a branch, create one now starting from 'This Detached Che $ui_comm delete 0.0 end $ui_comm edit reset $ui_comm edit modified false - rescan [list ui_status "Checked out '$name'."] + rescan [list ui_status [mc "Checked out '%s'." $name]] } else { repository_state commit_type HEAD MERGE_HEAD set PARENT $HEAD @@ -475,7 +475,7 @@ method _confirm_reset {cur} { pack [label $w.msg1 \ -anchor w \ -justify left \ - -text "Resetting '$name' to $new_expr will lose the following commits:" \ + -text [mc "Resetting '%s' to '%s' will lose the following commits:" $name $new_expr]\ ] -anchor w set list $w.list.l @@ -497,21 +497,21 @@ method _confirm_reset {cur} { pack [label $w.msg2 \ -anchor w \ -justify left \ - -text {Recovering lost commits may not be easy.} \ + -text [mc "Recovering lost commits may not be easy."] \ ] pack [label $w.msg3 \ -anchor w \ -justify left \ - -text "Reset '$name'?" \ + -text [mc "Reset '%s'?" $name] \ ] frame $w.buttons button $w.buttons.visualize \ - -text Visualize \ + -text [mc Visualize] \ -command $gitk pack $w.buttons.visualize -side left button $w.buttons.reset \ - -text Reset \ + -text [mc Reset] \ -command " set @reset_ok 1 destroy $w @@ -519,7 +519,7 @@ method _confirm_reset {cur} { pack $w.buttons.reset -side right button $w.buttons.cancel \ -default active \ - -text 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 @@ -575,13 +575,13 @@ method _toplevel {title} { } method _fatal {err} { - error_popup "Failed to set current branch. + error_popup [strcat [mc "Failed to set current branch. This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. -This should not have occurred. [appname] will now close and give up. +This should not have occurred. %s will now close and give up." [appname]] " -$err" +$err"] exit 1 } diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl new file mode 100644 index 0000000000..2bac50e149 --- /dev/null +++ b/git-gui/lib/choose_repository.tcl @@ -0,0 +1,1044 @@ +# git-gui Git repository chooser +# Copyright (C) 2007 Shawn Pearce + +class choose_repository { + +field top +field w +field w_body ; # Widget holding the center content +field w_next ; # Next button +field w_quit ; # Quit button +field o_cons ; # Console object (if active) +field w_types ; # List of type buttons in clone +field w_recentlist ; # Listbox containing recent repositories + +field done 0 ; # Finished picking the repository? +field local_path {} ; # Where this repository is locally +field origin_url {} ; # Where we are cloning from +field origin_name origin ; # What we shall call 'origin' +field clone_type hardlink ; # Type of clone to construct +field readtree_err ; # Error output from read-tree (if any) +field sorted_recent ; # recent repositories (sorted) + +constructor pick {} { + global M1T M1B + + make_toplevel top w + wm title $top [mc "Git Gui"] + + if {$top eq {.}} { + menu $w.mbar -tearoff 0 + $top configure -menu $w.mbar + + set m_repo $w.mbar.repository + $w.mbar add cascade \ + -label [mc Repository] \ + -menu $m_repo + menu $m_repo + + if {[is_MacOSX]} { + $w.mbar add cascade -label [mc Apple] -menu .mbar.apple + menu $w.mbar.apple + $w.mbar.apple add command \ + -label [mc "About %s" [appname]] \ + -command do_about + } else { + $w.mbar add cascade -label [mc Help] -menu $w.mbar.help + menu $w.mbar.help + $w.mbar.help add command \ + -label [mc "About %s" [appname]] \ + -command do_about + } + + wm protocol $top WM_DELETE_WINDOW exit + bind $top <$M1B-q> exit + bind $top <$M1B-Q> exit + bind $top <Key-Escape> exit + } else { + wm geometry $top "+[winfo rootx .]+[winfo rooty .]" + bind $top <Key-Escape> [list destroy $top] + set m_repo {} + } + + pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10 + + set w_body $w.body + set opts $w_body.options + frame $w_body + text $opts \ + -cursor $::cursor_ptr \ + -relief flat \ + -background [$w_body cget -background] \ + -wrap none \ + -spacing1 5 \ + -width 50 \ + -height 3 + pack $opts -anchor w -fill x + + $opts tag conf link_new -foreground blue -underline 1 + $opts tag bind link_new <1> [cb _next new] + $opts insert end [mc "Create New Repository"] link_new + $opts insert end "\n" + if {$m_repo ne {}} { + $m_repo add command \ + -command [cb _next new] \ + -accelerator $M1T-N \ + -label [mc "New..."] + bind $top <$M1B-n> [cb _next new] + bind $top <$M1B-N> [cb _next new] + } + + $opts tag conf link_clone -foreground blue -underline 1 + $opts tag bind link_clone <1> [cb _next clone] + $opts insert end [mc "Clone Existing Repository"] link_clone + $opts insert end "\n" + if {$m_repo ne {}} { + $m_repo add command \ + -command [cb _next clone] \ + -accelerator $M1T-C \ + -label [mc "Clone..."] + bind $top <$M1B-c> [cb _next clone] + bind $top <$M1B-C> [cb _next clone] + } + + $opts tag conf link_open -foreground blue -underline 1 + $opts tag bind link_open <1> [cb _next open] + $opts insert end [mc "Open Existing Repository"] link_open + $opts insert end "\n" + if {$m_repo ne {}} { + $m_repo add command \ + -command [cb _next open] \ + -accelerator $M1T-O \ + -label [mc "Open..."] + bind $top <$M1B-o> [cb _next open] + bind $top <$M1B-O> [cb _next open] + } + + $opts conf -state disabled + + set sorted_recent [_get_recentrepos] + if {[llength $sorted_recent] > 0} { + if {$m_repo ne {}} { + $m_repo add separator + $m_repo add command \ + -state disabled \ + -label [mc "Recent Repositories"] + } + + label $w_body.space + label $w_body.recentlabel \ + -anchor w \ + -text [mc "Open Recent Repository:"] + set w_recentlist $w_body.recentlist + text $w_recentlist \ + -cursor $::cursor_ptr \ + -relief flat \ + -background [$w_body.recentlabel cget -background] \ + -wrap none \ + -width 50 \ + -height 10 + $w_recentlist tag conf link \ + -foreground blue \ + -underline 1 + set home $::env(HOME) + if {[is_Cygwin]} { + set home [exec cygpath --windows --absolute $home] + } + set home "[file normalize $home]/" + set hlen [string length $home] + foreach p $sorted_recent { + set path $p + if {[string equal -length $hlen $home $p]} { + set p "~/[string range $p $hlen end]" + } + regsub -all "\n" $p "\\n" p + $w_recentlist insert end $p link + $w_recentlist insert end "\n" + + if {$m_repo ne {}} { + $m_repo add command \ + -command [cb _open_recent_path $path] \ + -label " $p" + } + } + $w_recentlist conf -state disabled + $w_recentlist tag bind link <1> [cb _open_recent %x,%y] + pack $w_body.space -anchor w -fill x + pack $w_body.recentlabel -anchor w -fill x + pack $w_recentlist -anchor w -fill x + } + pack $w_body -fill x -padx 10 -pady 10 + + frame $w.buttons + set w_next $w.buttons.next + set w_quit $w.buttons.quit + button $w_quit \ + -text [mc "Quit"] \ + -command exit + pack $w_quit -side right -padx 5 + pack $w.buttons -side bottom -fill x -padx 10 -pady 10 + + if {$m_repo ne {}} { + $m_repo add separator + $m_repo add command \ + -label [mc Quit] \ + -command exit \ + -accelerator $M1T-Q + } + + bind $top <Return> [cb _invoke_next] + bind $top <Visibility> " + [cb _center] + grab $top + focus $top + bind $top <Visibility> {} + " + wm deiconify $top + tkwait variable @done + + if {$top eq {.}} { + eval destroy [winfo children $top] + } +} + +proc _home {} { + if {[catch {set h $::env(HOME)}] + || ![file isdirectory $h]} { + set h . + } + return $h +} + +method _center {} { + set nx [winfo reqwidth $top] + set ny [winfo reqheight $top] + set rx [expr {([winfo screenwidth $top] - $nx) / 3}] + set ry [expr {([winfo screenheight $top] - $ny) / 3}] + wm geometry $top [format {+%d+%d} $rx $ry] +} + +method _invoke_next {} { + if {[winfo exists $w_next]} { + uplevel #0 [$w_next cget -command] + } +} + +proc _get_recentrepos {} { + set recent [list] + foreach p [get_config gui.recentrepo] { + if {[_is_git [file join $p .git]]} { + lappend recent $p + } + } + return [lsort $recent] +} + +proc _unset_recentrepo {p} { + regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p + git config --global --unset gui.recentrepo "^$p\$" +} + +proc _append_recentrepos {path} { + set path [file normalize $path] + set recent [get_config gui.recentrepo] + + if {[lindex $recent end] eq $path} { + return + } + + set i [lsearch $recent $path] + if {$i >= 0} { + _unset_recentrepo $path + set recent [lreplace $recent $i $i] + } + + lappend recent $path + git config --global --add gui.recentrepo $path + + while {[llength $recent] > 10} { + _unset_recentrepo [lindex $recent 0] + set recent [lrange $recent 1 end] + } +} + +method _open_recent {xy} { + set id [lindex [split [$w_recentlist index @$xy] .] 0] + set local_path [lindex $sorted_recent [expr {$id - 1}]] + _do_open2 $this +} + +method _open_recent_path {p} { + set local_path $p + _do_open2 $this +} + +method _next {action} { + destroy $w_body + if {![winfo exists $w_next]} { + button $w_next -default active + pack $w_next -side right -padx 5 -before $w_quit + } + _do_$action $this +} + +method _write_local_path {args} { + if {$local_path eq {}} { + $w_next conf -state disabled + } else { + $w_next conf -state normal + } +} + +method _git_init {} { + if {[file exists $local_path]} { + error_popup [mc "Location %s already exists." $local_path] + return 0 + } + + if {[catch {file mkdir $local_path} err]} { + error_popup [strcat \ + [mc "Failed to create repository %s:" $local_path] \ + "\n\n$err"] + return 0 + } + + if {[catch {cd $local_path} err]} { + error_popup [strcat \ + [mc "Failed to create repository %s:" $local_path] \ + "\n\n$err"] + return 0 + } + + if {[catch {git init} err]} { + error_popup [strcat \ + [mc "Failed to create repository %s:" $local_path] \ + "\n\n$err"] + return 0 + } + + _append_recentrepos [pwd] + set ::_gitdir .git + set ::_prefix {} + return 1 +} + +proc _is_git {path} { + if {[file exists [file join $path HEAD]] + && [file exists [file join $path objects]] + && [file exists [file join $path config]]} { + return 1 + } + if {[is_Cygwin]} { + if {[file exists [file join $path HEAD]] + && [file exists [file join $path objects.lnk]] + && [file exists [file join $path config.lnk]]} { + return 1 + } + } + return 0 +} + +proc _objdir {path} { + set objdir [file join $path .git objects] + if {[file isdirectory $objdir]} { + return $objdir + } + + set objdir [file join $path objects] + if {[file isdirectory $objdir]} { + return $objdir + } + + if {[is_Cygwin]} { + set objdir [file join $path .git objects.lnk] + if {[file isfile $objdir]} { + return [win32_read_lnk $objdir] + } + + set objdir [file join $path objects.lnk] + if {[file isfile $objdir]} { + return [win32_read_lnk $objdir] + } + } + + return {} +} + +###################################################################### +## +## Create New Repository + +method _do_new {} { + $w_next conf \ + -state disabled \ + -command [cb _do_new2] \ + -text [mc "Create"] + + frame $w_body + label $w_body.h \ + -font font_uibold \ + -text [mc "Create New Repository"] + pack $w_body.h -side top -fill x -pady 10 + pack $w_body -fill x -padx 10 + + frame $w_body.where + label $w_body.where.l -text [mc "Directory:"] + entry $w_body.where.t \ + -textvariable @local_path \ + -font font_diff \ + -width 50 + button $w_body.where.b \ + -text [mc "Browse"] \ + -command [cb _new_local_path] + + pack $w_body.where.b -side right + pack $w_body.where.l -side left + pack $w_body.where.t -fill x + pack $w_body.where -fill x + + trace add variable @local_path write [cb _write_local_path] + bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]] + update + focus $w_body.where.t +} + +method _new_local_path {} { + if {$local_path ne {}} { + set p [file dirname $local_path] + } else { + set p [_home] + } + + set p [tk_chooseDirectory \ + -initialdir $p \ + -parent $top \ + -title [mc "Git Repository"] \ + -mustexist false] + if {$p eq {}} return + + set p [file normalize $p] + if {[file isdirectory $p]} { + foreach i [glob \ + -directory $p \ + -tails \ + -nocomplain \ + * .*] { + switch -- $i { + . continue + .. continue + default { + error_popup [mc "Directory %s already exists." $p] + return + } + } + } + if {[catch {file delete $p} err]} { + error_popup [strcat \ + [mc "Directory %s already exists." $p] \ + "\n\n$err"] + return + } + } elseif {[file exists $p]} { + error_popup [mc "File %s already exists." $p] + return + } + set local_path $p +} + +method _do_new2 {} { + if {![_git_init $this]} { + return + } + set done 1 +} + +###################################################################### +## +## Clone Existing Repository + +method _do_clone {} { + $w_next conf \ + -state disabled \ + -command [cb _do_clone2] \ + -text [mc "Clone"] + + frame $w_body + label $w_body.h \ + -font font_uibold \ + -text [mc "Clone Existing Repository"] + pack $w_body.h -side top -fill x -pady 10 + pack $w_body -fill x -padx 10 + + set args $w_body.args + frame $w_body.args + pack $args -fill both + + label $args.origin_l -text [mc "URL:"] + entry $args.origin_t \ + -textvariable @origin_url \ + -font font_diff \ + -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:"] + entry $args.where_t \ + -textvariable @local_path \ + -font font_diff \ + -width 50 + button $args.where_b \ + -text [mc "Browse"] \ + -command [cb _new_local_path] + grid $args.where_l $args.where_t $args.where_b -sticky ew + + label $args.type_l -text [mc "Clone Type:"] + frame $args.type_f + set w_types [list] + lappend w_types [radiobutton $args.type_f.hardlink \ + -state disabled \ + -anchor w \ + -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \ + -variable @clone_type \ + -value hardlink] + lappend w_types [radiobutton $args.type_f.full \ + -state disabled \ + -anchor w \ + -text [mc "Full Copy (Slower, Redundant Backup)"] \ + -variable @clone_type \ + -value full] + lappend w_types [radiobutton $args.type_f.shared \ + -state disabled \ + -anchor w \ + -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \ + -variable @clone_type \ + -value shared] + foreach r $w_types { + pack $r -anchor w + } + grid $args.type_l $args.type_f -sticky new + + grid columnconfigure $args 1 -weight 1 + + trace add variable @local_path write [cb _update_clone] + trace add variable @origin_url write [cb _update_clone] + bind $w_body.h <Destroy> " + [list trace remove variable @local_path write [cb _update_clone]] + [list trace remove variable @origin_url write [cb _update_clone]] + " + update + focus $args.origin_t +} + +method _open_origin {} { + if {$origin_url ne {} && [file isdirectory $origin_url]} { + set p $origin_url + } else { + set p [_home] + } + + set p [tk_chooseDirectory \ + -initialdir $p \ + -parent $top \ + -title [mc "Git Repository"] \ + -mustexist true] + if {$p eq {}} return + + set p [file normalize $p] + if {![_is_git [file join $p .git]] && ![_is_git $p]} { + error_popup [mc "Not a Git repository: %s" [file tail $p]] + return + } + set origin_url $p +} + +method _update_clone {args} { + if {$local_path ne {} && $origin_url ne {}} { + $w_next conf -state normal + } else { + $w_next conf -state disabled + } + + if {$origin_url ne {} && + ( [_is_git [file join $origin_url .git]] + || [_is_git $origin_url])} { + set e normal + if {[[lindex $w_types 0] cget -state] eq {disabled}} { + set clone_type hardlink + } + } else { + set e disabled + set clone_type full + } + + foreach r $w_types { + $r conf -state $e + } +} + +method _do_clone2 {} { + if {[file isdirectory $origin_url]} { + set origin_url [file normalize $origin_url] + } + + if {$clone_type eq {hardlink} && ![file isdirectory $origin_url]} { + error_popup [mc "Standard only available for local repository."] + return + } + if {$clone_type eq {shared} && ![file isdirectory $origin_url]} { + error_popup [mc "Shared only available for local repository."] + return + } + + if {$clone_type eq {hardlink} || $clone_type eq {shared}} { + set objdir [_objdir $origin_url] + if {$objdir eq {}} { + error_popup [mc "Not a Git repository: %s" [file tail $origin_url]] + return + } + } + + set giturl $origin_url + if {[is_Cygwin] && [file isdirectory $giturl]} { + set giturl [exec cygpath --unix --absolute $giturl] + if {$clone_type eq {shared}} { + set objdir [exec cygpath --unix --absolute $objdir] + } + } + + if {![_git_init $this]} return + set local_path [pwd] + + if {[catch { + git config remote.$origin_name.url $giturl + git config remote.$origin_name.fetch +refs/heads/*:refs/remotes/$origin_name/* + } err]} { + error_popup [strcat [mc "Failed to configure origin"] "\n\n$err"] + return + } + + destroy $w_body $w_next + + switch -exact -- $clone_type { + hardlink { + set o_cons [status_bar::two_line $w_body] + pack $w_body -fill x -padx 10 -pady 10 + + $o_cons start \ + [mc "Counting objects"] \ + [mc "buckets"] + update + + if {[file exists [file join $objdir info alternates]]} { + set pwd [pwd] + if {[catch { + file mkdir [gitdir objects info] + set f_in [open [file join $objdir info alternates] r] + set f_cp [open [gitdir objects info alternates] w] + fconfigure $f_in -translation binary -encoding binary + fconfigure $f_cp -translation binary -encoding binary + cd $objdir + while {[gets $f_in line] >= 0} { + if {[is_Cygwin]} { + puts $f_cp [exec cygpath --unix --absolute $line] + } else { + puts $f_cp [file normalize $line] + } + } + close $f_in + close $f_cp + cd $pwd + } err]} { + catch {cd $pwd} + _clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err] + return + } + } + + set tolink [list] + set buckets [glob \ + -tails \ + -nocomplain \ + -directory [file join $objdir] ??] + set bcnt [expr {[llength $buckets] + 2}] + set bcur 1 + $o_cons update $bcur $bcnt + update + + file mkdir [file join .git objects pack] + foreach i [glob -tails -nocomplain \ + -directory [file join $objdir pack] *] { + lappend tolink [file join pack $i] + } + $o_cons update [incr bcur] $bcnt + update + + foreach i $buckets { + file mkdir [file join .git objects $i] + foreach j [glob -tails -nocomplain \ + -directory [file join $objdir $i] *] { + lappend tolink [file join $i $j] + } + $o_cons update [incr bcur] $bcnt + update + } + $o_cons stop + + if {$tolink eq {}} { + info_popup [strcat \ + [mc "Nothing to clone from %s." $origin_url] \ + "\n" \ + [mc "The 'master' branch has not been initialized."] \ + ] + destroy $w_body + set done 1 + return + } + + set i [lindex $tolink 0] + if {[catch { + file link -hard \ + [file join .git objects $i] \ + [file join $objdir $i] + } err]} { + info_popup [mc "Hardlinks are unavailable. Falling back to copying."] + set i [_copy_files $this $objdir $tolink] + } else { + set i [_link_files $this $objdir [lrange $tolink 1 end]] + } + if {!$i} return + + destroy $w_body + } + full { + set o_cons [console::embed \ + $w_body \ + [mc "Cloning from %s" $origin_url]] + pack $w_body -fill both -expand 1 -padx 10 + $o_cons exec \ + [list git fetch --no-tags -k $origin_name] \ + [cb _do_clone_tags] + } + shared { + set fd [open [gitdir objects info alternates] w] + fconfigure $fd -translation binary + puts $fd $objdir + close $fd + } + } + + if {$clone_type eq {hardlink} || $clone_type eq {shared}} { + if {![_clone_refs $this]} return + set pwd [pwd] + if {[catch { + cd $origin_url + set HEAD [git rev-parse --verify HEAD^0] + } err]} { + _clone_failed $this [mc "Not a Git repository: %s" [file tail $origin_url]] + return 0 + } + cd $pwd + _do_clone_checkout $this $HEAD + } +} + +method _copy_files {objdir tocopy} { + $o_cons start \ + [mc "Copying objects"] \ + [mc "KiB"] + set tot 0 + set cmp 0 + foreach p $tocopy { + incr tot [file size [file join $objdir $p]] + } + foreach p $tocopy { + if {[catch { + set f_in [open [file join $objdir $p] r] + set f_cp [open [file join .git objects $p] w] + fconfigure $f_in -translation binary -encoding binary + fconfigure $f_cp -translation binary -encoding binary + + while {![eof $f_in]} { + incr cmp [fcopy $f_in $f_cp -size 16384] + $o_cons update \ + [expr {$cmp / 1024}] \ + [expr {$tot / 1024}] + update + } + + close $f_in + close $f_cp + } err]} { + _clone_failed $this [mc "Unable to copy object: %s" $err] + return 0 + } + } + return 1 +} + +method _link_files {objdir tolink} { + set total [llength $tolink] + $o_cons start \ + [mc "Linking objects"] \ + [mc "objects"] + for {set i 0} {$i < $total} {} { + set p [lindex $tolink $i] + if {[catch { + file link -hard \ + [file join .git objects $p] \ + [file join $objdir $p] + } err]} { + _clone_failed $this [mc "Unable to hardlink object: %s" $err] + return 0 + } + + incr i + if {$i % 5 == 0} { + $o_cons update $i $total + update + } + } + return 1 +} + +method _clone_refs {} { + set pwd [pwd] + if {[catch {cd $origin_url} err]} { + error_popup [mc "Not a Git repository: %s" [file tail $origin_url]] + return 0 + } + set fd_in [git_read for-each-ref \ + --tcl \ + {--format=list %(refname) %(objectname) %(*objectname)}] + cd $pwd + + set fd [open [gitdir packed-refs] w] + fconfigure $fd -translation binary + puts $fd "# pack-refs with: peeled" + while {[gets $fd_in line] >= 0} { + set line [eval $line] + set refn [lindex $line 0] + set robj [lindex $line 1] + set tobj [lindex $line 2] + + if {[regsub ^refs/heads/ $refn \ + "refs/remotes/$origin_name/" refn]} { + puts $fd "$robj $refn" + } elseif {[string match refs/tags/* $refn]} { + puts $fd "$robj $refn" + if {$tobj ne {}} { + puts $fd "^$tobj" + } + } + } + close $fd_in + close $fd + return 1 +} + +method _do_clone_tags {ok} { + if {$ok} { + $o_cons exec \ + [list git fetch --tags -k $origin_name] \ + [cb _do_clone_HEAD] + } else { + $o_cons done $ok + _clone_failed $this [mc "Cannot fetch branches and objects. See console output for details."] + } +} + +method _do_clone_HEAD {ok} { + if {$ok} { + $o_cons exec \ + [list git fetch $origin_name HEAD] \ + [cb _do_clone_full_end] + } else { + $o_cons done $ok + _clone_failed $this [mc "Cannot fetch tags. See console output for details."] + } +} + +method _do_clone_full_end {ok} { + $o_cons done $ok + + if {$ok} { + destroy $w_body + + set HEAD {} + if {[file exists [gitdir FETCH_HEAD]]} { + set fd [open [gitdir FETCH_HEAD] r] + while {[gets $fd line] >= 0} { + if {[regexp "^(.{40})\t\t" $line line HEAD]} { + break + } + } + close $fd + } + + catch {git pack-refs} + _do_clone_checkout $this $HEAD + } else { + _clone_failed $this [mc "Cannot determine HEAD. See console output for details."] + } +} + +method _clone_failed {{why {}}} { + if {[catch {file delete -force $local_path} err]} { + set why [strcat \ + $why \ + "\n\n" \ + [mc "Unable to cleanup %s" $local_path] \ + "\n\n" \ + $err] + } + if {$why ne {}} { + update + error_popup [strcat [mc "Clone failed."] "\n" $why] + } +} + +method _do_clone_checkout {HEAD} { + if {$HEAD eq {}} { + info_popup [strcat \ + [mc "No default branch obtained."] \ + "\n" \ + [mc "The 'master' branch has not been initialized."] \ + ] + set done 1 + return + } + if {[catch { + git update-ref HEAD $HEAD^0 + } err]} { + info_popup [strcat \ + [mc "Cannot resolve %s as a commit." $HEAD^0] \ + "\n $err" \ + "\n" \ + [mc "The 'master' branch has not been initialized."] \ + ] + set done 1 + return + } + + set o_cons [status_bar::two_line $w_body] + pack $w_body -fill x -padx 10 -pady 10 + $o_cons start \ + [mc "Creating working directory"] \ + [mc "files"] + + set readtree_err {} + set fd [git_read --stderr read-tree \ + -m \ + -u \ + -v \ + HEAD \ + HEAD \ + ] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [cb _readtree_wait $fd] +} + +method _readtree_wait {fd} { + set buf [read $fd] + $o_cons update_meter $buf + append readtree_err $buf + + fconfigure $fd -blocking 1 + if {![eof $fd]} { + fconfigure $fd -blocking 0 + return + } + + if {[catch {close $fd}]} { + set err $readtree_err + regsub {^fatal: } $err {} err + error_popup [strcat \ + [mc "Initial file checkout failed."] \ + "\n\n$err"] + return + } + + set done 1 +} + +###################################################################### +## +## Open Existing Repository + +method _do_open {} { + $w_next conf \ + -state disabled \ + -command [cb _do_open2] \ + -text [mc "Open"] + + frame $w_body + label $w_body.h \ + -font font_uibold \ + -text [mc "Open Existing Repository"] + pack $w_body.h -side top -fill x -pady 10 + pack $w_body -fill x -padx 10 + + frame $w_body.where + label $w_body.where.l -text [mc "Repository:"] + entry $w_body.where.t \ + -textvariable @local_path \ + -font font_diff \ + -width 50 + button $w_body.where.b \ + -text [mc "Browse"] \ + -command [cb _open_local_path] + + pack $w_body.where.b -side right + pack $w_body.where.l -side left + pack $w_body.where.t -fill x + pack $w_body.where -fill x + + trace add variable @local_path write [cb _write_local_path] + bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]] + update + focus $w_body.where.t +} + +method _open_local_path {} { + if {$local_path ne {}} { + set p $local_path + } else { + set p [_home] + } + + set p [tk_chooseDirectory \ + -initialdir $p \ + -parent $top \ + -title [mc "Git Repository"] \ + -mustexist true] + if {$p eq {}} return + + set p [file normalize $p] + if {![_is_git [file join $p .git]]} { + error_popup [mc "Not a Git repository: %s" [file tail $p]] + return + } + set local_path $p +} + +method _do_open2 {} { + if {![_is_git [file join $local_path .git]]} { + error_popup [mc "Not a Git repository: %s" [file tail $local_path]] + return + } + + if {[catch {cd $local_path} err]} { + error_popup [strcat \ + [mc "Failed to open repository %s:" $local_path] \ + "\n\n$err"] + return + } + + _append_recentrepos [pwd] + set ::_gitdir .git + set ::_prefix {} + set done 1 +} + +} diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl index ec064b3e13..a063c5bc49 100644 --- a/git-gui/lib/choose_rev.tcl +++ b/git-gui/lib/choose_rev.tcl @@ -50,14 +50,14 @@ constructor _new {path unmerged_only title} { if {$is_detached} { radiobutton $w.detachedhead_r \ -anchor w \ - -text {This Detached Checkout} \ + -text [mc "This Detached Checkout"] \ -value HEAD \ -variable @revtype grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2 } radiobutton $w.expr_r \ - -text {Revision Expression:} \ + -text [mc "Revision Expression:"] \ -value expr \ -variable @revtype entry $w.expr_t \ @@ -71,17 +71,17 @@ constructor _new {path unmerged_only title} { frame $w.types radiobutton $w.types.head_r \ - -text {Local Branch} \ + -text [mc "Local Branch"] \ -value head \ -variable @revtype pack $w.types.head_r -side left radiobutton $w.types.trck_r \ - -text {Tracking Branch} \ + -text [mc "Tracking Branch"] \ -value trck \ -variable @revtype pack $w.types.trck_r -side left radiobutton $w.types.tag_r \ - -text {Tag} \ + -text [mc "Tag"] \ -value tag \ -variable @revtype pack $w.types.tag_r -side left @@ -133,13 +133,13 @@ constructor _new {path unmerged_only title} { append fmt { %(objecttype)} append fmt { %(objectname)} append fmt { [concat %(taggername) %(authorname)]} - append fmt { [concat %(taggerdate) %(authordate)]} + append fmt { [reformat_date [concat %(taggerdate) %(authordate)]]} append fmt { %(subject)} append fmt {] [list} append fmt { %(*objecttype)} append fmt { %(*objectname)} append fmt { %(*authorname)} - append fmt { %(*authordate)} + append fmt { [reformat_date %(*authordate)]} append fmt { %(*subject)} append fmt {]} set all_refn [list] @@ -314,7 +314,7 @@ method commit_or_die {} { } set top [winfo toplevel $w] - set msg "Invalid revision: [get $this]\n\n$err" + set msg [strcat [mc "Invalid revision: %s" [get $this]] "\n\n$err"] tk_messageBox \ -icon error \ -type ok \ @@ -335,7 +335,7 @@ method _expr {} { if {$i ne {}} { return [lindex $cur_specs $i 1] } else { - error "No revision selected." + error [mc "No revision selected."] } } @@ -343,7 +343,7 @@ method _expr {} { if {$c_expr ne {}} { return $c_expr } else { - error "Revision expression is empty." + error [mc "Revision expression is empty."] } } HEAD { return HEAD } @@ -527,14 +527,14 @@ method _open_tooltip {} { set last [_reflog_last $this [lindex $spec 1]] if {$last ne {}} { $tooltip_t insert end "\n" - $tooltip_t insert end "updated" + $tooltip_t insert end [mc "Updated"] $tooltip_t insert end " $last" } $tooltip_t insert end "\n" if {$tag ne {}} { $tooltip_t insert end "\n" - $tooltip_t insert end "tag" section_header + $tooltip_t insert end [mc "Tag"] section_header $tooltip_t insert end " [lindex $tag 1]\n" $tooltip_t insert end [lindex $tag 2] $tooltip_t insert end " ([lindex $tag 3])\n" @@ -544,7 +544,7 @@ method _open_tooltip {} { if {$cmit ne {}} { $tooltip_t insert end "\n" - $tooltip_t insert end "commit" section_header + $tooltip_t insert end [mc "Commit@@noun"] section_header $tooltip_t insert end " [lindex $cmit 1]\n" $tooltip_t insert end [lindex $cmit 2] $tooltip_t insert end " ([lindex $cmit 3])\n" @@ -553,11 +553,11 @@ method _open_tooltip {} { if {[llength $spec] > 2} { $tooltip_t insert end "\n" - $tooltip_t insert end "remote" section_header + $tooltip_t insert end [mc "Remote"] section_header $tooltip_t insert end " [lindex $spec 2]\n" - $tooltip_t insert end "url" + $tooltip_t insert end [mc "URL"] $tooltip_t insert end " $remote_url([lindex $spec 2])\n" - $tooltip_t insert end "branch" + $tooltip_t insert end [mc "Branch"] $tooltip_t insert end " [lindex $spec 3]" } @@ -583,7 +583,7 @@ method _reflog_last {name} { } if {$last ne {}} { - set last [clock format $last -format {%a %b %e %H:%M:%S %Y}] + set last [format_date $last] } set reflog_last($name) $last return $last diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 57238129e4..10b0430f54 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -6,19 +6,19 @@ proc load_last_commit {} { global repo_config if {[llength $PARENT] == 0} { - error_popup {There is nothing to amend. + error_popup [mc "There is nothing to amend. You are about to create the initial commit. There is no commit before this to amend. -} +"] return } repository_state curType curHEAD curMERGE_HEAD if {$curType eq {merge}} { - error_popup {Cannot amend while merging. + error_popup [mc "Cannot amend while merging. You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. -} +"] return } @@ -46,7 +46,7 @@ You are currently in the middle of a merge that has not been fully completed. Y } set msg [string trim $msg] } err]} { - error_popup "Error loading commit data for amend:\n\n$err" + error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"] return } @@ -73,12 +73,12 @@ proc committer_ident {} { if {$GIT_COMMITTER_IDENT eq {}} { if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { - error_popup "Unable to obtain your identity:\n\n$err" + error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"] return {} } if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ $me me GIT_COMMITTER_IDENT]} { - error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" + error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"] return {} } } @@ -130,12 +130,12 @@ proc commit_tree {} { && $curType eq {normal} && $curHEAD eq $HEAD} { } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. + info_popup [mc "Last scanned state does not match repository state. Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. The rescan will be automatically started now. -} +"] unlock_index rescan ui_ready return @@ -151,26 +151,26 @@ The rescan will be automatically started now. D? - M? {set files_ready 1} U? { - error_popup "Unmerged files cannot be committed. + error_popup [mc "Unmerged files cannot be committed. -File [short_path $path] has merge conflicts. You must resolve them and stage the file before committing. -" +File %s has merge conflicts. You must resolve them and stage the file before committing. +" [short_path $path]] unlock_index return } default { - error_popup "Unknown file state [lindex $s 0] detected. + error_popup [mc "Unknown file state %s detected. -File [short_path $path] cannot be committed by this program. -" +File %s cannot be committed by this program. +" [lindex $s 0] [short_path $path]] } } } if {!$files_ready && ![string match *merge $curType]} { - info_popup {No changes to commit. + info_popup [mc "No changes to commit. You must stage at least 1 file before you can commit. -} +"] unlock_index return } @@ -180,14 +180,14 @@ You must stage at least 1 file before you can commit. set msg [string trim [$ui_comm get 1.0 end]] regsub -all -line {[ \t\r]+$} $msg {} msg if {$msg eq {}} { - error_popup {Please supply a commit message. + error_popup [mc "Please supply a commit message. A good commit message has the following format: - First line: Describe in one sentance what you did. - Second line: Blank - Remaining lines: Describe why this change is good. -} +"] unlock_index return } @@ -254,7 +254,7 @@ proc commit_committree {fd_wt curHEAD msg} { gets $fd_wt tree_id if {[catch {close $fd_wt} err]} { - error_popup "write-tree failed:\n\n$err" + error_popup [strcat [mc "write-tree failed:"] "\n\n$err"] ui_status {Commit failed.} unlock_index return @@ -272,18 +272,18 @@ proc commit_committree {fd_wt curHEAD msg} { && [string length $old_tree] == 45} { set old_tree [string range $old_tree 5 end] } else { - error "Commit $PARENT appears to be corrupt" + error [mc "Commit %s appears to be corrupt" $PARENT] } if {$tree_id eq $old_tree} { - info_popup {No changes to commit. + info_popup [mc "No changes to commit. No files were modified by this commit and it was not a merge commit. A rescan will be automatically started now. -} +"] unlock_index - rescan {ui_status {No changes to commit.}} + rescan {ui_status [mc "No changes to commit."]} return } } @@ -300,7 +300,7 @@ A rescan will be automatically started now. if {$use_enc ne {}} { fconfigure $msg_wt -encoding $use_enc } else { - puts stderr "warning: Tcl does not support encoding '$enc'." + puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc] fconfigure $msg_wt -encoding utf-8 } puts -nonewline $msg_wt $msg @@ -314,7 +314,7 @@ A rescan will be automatically started now. } lappend cmd <$msg_p if {[catch {set cmt_id [eval git $cmd]} err]} { - error_popup "commit-tree failed:\n\n$err" + error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] ui_status {Commit failed.} unlock_index return @@ -336,7 +336,7 @@ A rescan will be automatically started now. if {[catch { git update-ref -m $reflogm HEAD $cmt_id $curHEAD } err]} { - error_popup "update-ref failed:\n\n$err" + error_popup [strcat [mc "update-ref failed:"] "\n\n$err"] ui_status {Commit failed.} unlock_index return @@ -427,5 +427,5 @@ A rescan will be automatically started now. display_all_files unlock_index reshow_diff - ui_status "Created commit [string range $cmt_id 0 7]: $subject" + ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject] } diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index b038a78358..5597188d80 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -6,6 +6,7 @@ class console { field t_short field t_long field w +field w_t field console_cr field is_toplevel 1; # are we our own window? @@ -36,6 +37,7 @@ method _init {} { } set console_cr 1.0 + set w_t $w.m.t frame $w.m label $w.m.l1 \ @@ -43,51 +45,47 @@ method _init {} { -anchor w \ -justify left \ -font font_uibold - text $w.m.t \ + text $w_t \ -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -wrap none \ -font font_diff \ -state disabled \ - -xscrollcommand [list $w.m.sbx set] \ - -yscrollcommand [list $w.m.sby set] - label $w.m.s -text {Working... please wait...} \ + -xscrollcommand [cb _sb_set $w.m.sbx h] \ + -yscrollcommand [cb _sb_set $w.m.sby v] + label $w.m.s -text [mc "Working... please wait..."] \ -anchor w \ -justify left \ -font font_uibold - scrollbar $w.m.sbx -command [list $w.m.t xview] -orient h - scrollbar $w.m.sby -command [list $w.m.t yview] pack $w.m.l1 -side top -fill x pack $w.m.s -side bottom -fill x - pack $w.m.sbx -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 + pack $w_t -side left -fill both -expand 1 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy" \ - -command "tk_textCopy $w.m.t" - $w.ctxm add command -label "Select All" \ - -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" - $w.ctxm add command -label "Copy All" \ + $w.ctxm add command -label [mc "Copy"] \ + -command "tk_textCopy $w_t" + $w.ctxm add command -label [mc "Select All"] \ + -command "focus $w_t;$w_t tag add sel 0.0 end" + $w.ctxm add command -label [mc "Copy All"] \ -command " - $w.m.t tag add sel 0.0 end - tk_textCopy $w.m.t - $w.m.t tag remove sel 0.0 end + $w_t tag add sel 0.0 end + tk_textCopy $w_t + $w_t tag remove sel 0.0 end " if {$is_toplevel} { - button $w.ok -text {Close} \ + button $w.ok -text [mc "Close"] \ -state disabled \ -command [list destroy $w] pack $w.ok -side bottom -anchor e -pady 10 -padx 10 bind $w <Visibility> [list focus $w] } - bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" - bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" - bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" + bind_button3 $w_t "tk_popup $w.ctxm %X %Y" + bind $w_t <$M1B-Key-a> "$w_t tag add sel 0.0 end;break" + bind $w_t <$M1B-Key-A> "$w_t tag add sel 0.0 end;break" } method exec {cmd {after {}}} { @@ -104,8 +102,8 @@ method exec {cmd {after {}}} { method _read {fd after} { set buf [read $fd] if {$buf ne {}} { - if {![winfo exists $w.m.t]} {_init $this} - $w.m.t conf -state normal + if {![winfo exists $w_t]} {_init $this} + $w_t conf -state normal set c 0 set n [string length $buf] while {$c < $n} { @@ -115,20 +113,20 @@ method _read {fd after} { if {$lf < 0} {set lf [expr {$n + 1}]} if {$lf < $cr} { - $w.m.t insert end [string range $buf $c $lf] - set console_cr [$w.m.t index {end -1c}] + $w_t insert end [string range $buf $c $lf] + set console_cr [$w_t index {end -1c}] set c $lf incr c } else { - $w.m.t delete $console_cr end - $w.m.t insert end "\n" - $w.m.t insert end [string range $buf $c [expr {$cr - 1}]] + $w_t delete $console_cr end + $w_t insert end "\n" + $w_t insert end [string range $buf $c [expr {$cr - 1}]] set c $cr incr c } } - $w.m.t conf -state disabled - $w.m.t see end + $w_t conf -state disabled + $w_t see end } fconfigure $fd -blocking 1 @@ -171,33 +169,50 @@ method chain {cmdlist {ok 1}} { } method insert {txt} { - if {![winfo exists $w.m.t]} {_init $this} - $w.m.t conf -state normal - $w.m.t insert end "$txt\n" - set console_cr [$w.m.t index {end -1c}] - $w.m.t conf -state disabled + if {![winfo exists $w_t]} {_init $this} + $w_t conf -state normal + $w_t insert end "$txt\n" + set console_cr [$w_t index {end -1c}] + $w_t conf -state disabled } method done {ok} { if {$ok} { if {[winfo exists $w.m.s]} { - $w.m.s conf -background green -text {Success} + bind $w.m.s <Destroy> [list delete_this $this] + $w.m.s conf -background green -text [mc "Success"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok } + } else { + delete_this } } else { if {![winfo exists $w.m.s]} { _init $this } - $w.m.s conf -background red -text {Error: Command Failed} + bind $w.m.s <Destroy> [list delete_this $this] + $w.m.s conf -background red -text [mc "Error: Command Failed"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok } } - delete_this +} + +method _sb_set {sb orient first last} { + if {![winfo exists $sb]} { + if {$first == $last || ($first == 0 && $last == 1)} return + if {$orient eq {h}} { + scrollbar $sb -orient h -command [list $w_t xview] + pack $sb -fill x -side bottom -before $w_t + } else { + scrollbar $sb -orient v -command [list $w_t yview] + pack $sb -fill y -side right -before $w_t + } + } + $sb set $first $last } } diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index 0657cc2245..d66aa3fe33 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -24,14 +24,14 @@ proc do_stats {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text {Database Statistics} + label $w.header -text [mc "Database Statistics"] pack $w.header -side top -fill x frame $w.buttons -border 1 - button $w.buttons.close -text Close \ + button $w.buttons.close -text [mc Close] \ -default active \ -command [list destroy $w] - button $w.buttons.gc -text {Compress Database} \ + button $w.buttons.gc -text [mc "Compress Database"] \ -default normal \ -command "destroy $w;do_gc" pack $w.buttons.close -side right @@ -40,16 +40,16 @@ proc do_stats {} { frame $w.stat -borderwidth 1 -relief solid foreach s { - {count {Number of loose objects}} - {size {Disk space used by loose objects} { KiB}} - {in-pack {Number of packed objects}} - {packs {Number of packs}} - {size-pack {Disk space used by packed objects} { KiB}} - {prune-packable {Packed objects waiting for pruning}} - {garbage {Garbage files}} + {count {mc "Number of loose objects"}} + {size {mc "Disk space used by loose objects"} { KiB}} + {in-pack {mc "Number of packed objects"}} + {packs {mc "Number of packs"}} + {size-pack {mc "Disk space used by packed objects"} { KiB}} + {prune-packable {mc "Packed objects waiting for pruning"}} + {garbage {mc "Garbage files"}} } { set name [lindex $s 0] - set label [lindex $s 1] + set label [eval [lindex $s 1]] if {[catch {set value $stats($name)}]} continue if {[llength $s] > 2} { set value "$value[lindex $s 2]" @@ -64,12 +64,12 @@ proc do_stats {} { bind $w <Visibility> "grab $w; focus $w.buttons.close" bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [list destroy $w] - wm title $w "[appname] ([reponame]): Database Statistics" + wm title $w [append "[appname] ([reponame]): " [mc "Database Statistics"]] tkwait window $w } proc do_gc {} { - set w [console::new {gc} {Compressing the object database}] + set w [console::new {gc} [mc "Compressing the object database"]] console::chain $w { {exec git pack-refs --prune} {exec git reflog expire --all} @@ -80,7 +80,7 @@ proc do_gc {} { proc do_fsck_objects {} { set w [console::new {fsck-objects} \ - {Verifying the object database with fsck-objects}] + [mc "Verifying the object database with fsck-objects"]] set cmd [list git fsck-objects] lappend cmd --full lappend cmd --cache @@ -105,11 +105,11 @@ proc hint_gc {} { set objects_current [expr {$objects_current * 256}] set object_limit [expr {$object_limit * 256}] if {[ask_popup \ - "This repository currently has approximately $objects_current loose objects. + [mc "This repository currently has approximately %i loose objects. -To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist. +To maintain optimal performance it is strongly recommended that you compress the database when more than %i loose objects exist. -Compress the database now?"] eq yes} { +Compress the database now?" $objects_current $object_limit]] eq yes} { do_gc } } diff --git a/git-gui/lib/date.tcl b/git-gui/lib/date.tcl new file mode 100644 index 0000000000..abe82992b6 --- /dev/null +++ b/git-gui/lib/date.tcl @@ -0,0 +1,53 @@ +# git-gui date processing support +# Copyright (C) 2007 Shawn Pearce + +set git_month(Jan) 1 +set git_month(Feb) 2 +set git_month(Mar) 3 +set git_month(Apr) 4 +set git_month(May) 5 +set git_month(Jun) 6 +set git_month(Jul) 7 +set git_month(Aug) 8 +set git_month(Sep) 9 +set git_month(Oct) 10 +set git_month(Nov) 11 +set git_month(Dec) 12 + +proc parse_git_date {s} { + if {$s eq {}} { + return {} + } + + if {![regexp \ + {^... (...) (\d{1,2}) (\d\d):(\d\d):(\d\d) (\d{4}) ([+-]?)(\d\d)(\d\d)$} $s s \ + month day hr mm ss yr ew tz_h tz_m]} { + error [mc "Invalid date from Git: %s" $s] + } + + set s [clock scan [format {%4.4i%2.2i%2.2iT%2s%2s%2s} \ + $yr $::git_month($month) $day \ + $hr $mm $ss] \ + -gmt 1] + + regsub ^0 $tz_h {} tz_h + regsub ^0 $tz_m {} tz_m + switch -- $ew { + - {set ew +} + + {set ew -} + {} {set ew -} + } + + return [expr "$s $ew ($tz_h * 3600 + $tz_m * 60)"] +} + +proc format_date {s} { + if {$s eq {}} { + return {} + } + return [clock format $s -format {%a %b %e %H:%M:%S %Y}] +} + +proc reformat_date {s} { + return [format_date [parse_git_date $s]] +} diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 694834ab7a..43565e412f 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -39,13 +39,13 @@ proc handle_empty_diff {} { set s $file_states($path) if {[lindex $s 0] ne {_M}} return - info_popup "No differences detected. + info_popup [mc "No differences detected. -[short_path $path] has no changes. +%s has no changes. The modification date of this file was updated by another application, but the content within the file was not changed. -A rescan will be automatically started to find other files which may have the same state." +A rescan will be automatically started to find other files which may have the same state." [short_path $path]] clear_diff display_file $path __ @@ -78,7 +78,7 @@ proc show_diff {path w {lno {}}} { set current_diff_path $path set current_diff_side $w set current_diff_header {} - ui_status "Loading diff of [escape_path $path]..." + ui_status [mc "Loading diff of %s..." [escape_path $path]] # - Git won't give us the diff, there's nothing to compare to! # @@ -111,13 +111,16 @@ proc show_diff {path w {lno {}}} { } err ]} { set diff_active 0 unlock_index - ui_status "Unable to display [escape_path $path]" - error_popup "Error loading file:\n\n$err" + ui_status [mc "Unable to display %s" [escape_path $path]] + error_popup [strcat [mc "Error loading file:"] "\n\n$err"] return } $ui_diff conf -state normal if {$type eq {submodule}} { - $ui_diff insert end "* Git Repository (subproject)\n" d_@ + $ui_diff insert end [append \ + "* " \ + [mc "Git Repository (subproject)"] \ + "\n"] d_@ } elseif {![catch {set type [exec file $path]}]} { set n [string length $path] if {[string equal -length $n $path $type]} { @@ -128,7 +131,7 @@ proc show_diff {path w {lno {}}} { } if {[string first "\0" $content] != -1} { $ui_diff insert end \ - "* Binary file (not showing content)." \ + [mc "* Binary file (not showing content)."] \ d_@ } else { if {$sz > $max_sz} { @@ -178,8 +181,8 @@ proc show_diff {path w {lno {}}} { if {[catch {set fd [eval git_read --nice $cmd]} err]} { set diff_active 0 unlock_index - ui_status "Unable to display [escape_path $path]" - error_popup "Error loading diff:\n\n$err" + ui_status [mc "Unable to display %s" [escape_path $path]] + error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] return } @@ -296,14 +299,14 @@ proc apply_hunk {x y} { set apply_cmd {apply --cached --whitespace=nowarn} set mi [lindex $file_states($current_diff_path) 0] if {$current_diff_side eq $ui_index} { - set mode unstage + set failed_msg [mc "Failed to unstage selected hunk."] lappend apply_cmd --reverse if {[string index $mi 0] ne {M}} { unlock_index return } } else { - set mode stage + set failed_msg [mc "Failed to stage selected hunk."] if {[string index $mi 1] ne {M}} { unlock_index return @@ -328,7 +331,7 @@ proc apply_hunk {x y} { puts -nonewline $p $current_diff_header puts -nonewline $p [$ui_diff get $s_lno $e_lno] close $p} err]} { - error_popup "Failed to $mode selected hunk.\n\n$err" + error_popup [append $failed_msg "\n\n$err"] unlock_index return } diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 16a22187b2..13565b7ab0 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -9,7 +9,7 @@ proc error_popup {msg} { set cmd [list tk_messageBox \ -icon error \ -type ok \ - -title "$title: error" \ + -title [append "$title: " [mc "error"]] \ -message $msg] if {[winfo ismapped .]} { lappend cmd -parent . @@ -25,7 +25,7 @@ proc warn_popup {msg} { set cmd [list tk_messageBox \ -icon warning \ -type ok \ - -title "$title: warning" \ + -title [append "$title: " [mc "warning"]] \ -message $msg] if {[winfo ismapped .]} { lappend cmd -parent . @@ -78,7 +78,7 @@ proc hook_failed_popup {hook msg} { -font font_diff \ -yscrollcommand [list $w.m.sby set] label $w.m.l2 \ - -text {You must correct the above errors before committing.} \ + -text [mc "You must correct the above errors before committing."] \ -anchor w \ -justify left \ -font font_uibold @@ -99,6 +99,6 @@ proc hook_failed_popup {hook msg} { bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "destroy $w" - wm title $w "[appname] ([reponame]): error" + wm title $w [append "[appname] ([reponame]): " [mc "error"]] tkwait window $w } diff --git a/git-gui/lib/git-gui.ico b/git-gui/lib/git-gui.ico Binary files differnew file mode 100644 index 0000000000..563dd66238 --- /dev/null +++ b/git-gui/lib/git-gui.ico diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 44689ab63b..a0b22f2945 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -1,6 +1,56 @@ # git-gui index (add/remove) support # Copyright (C) 2006, 2007 Shawn Pearce +proc _delete_indexlock {} { + if {[catch {file delete -- [gitdir index.lock]} err]} { + error_popup [strcat [mc "Unable to unlock the index."] "\n\n$err"] + } +} + +proc _close_updateindex {fd after} { + fconfigure $fd -blocking 1 + if {[catch {close $fd} err]} { + set w .indexfried + toplevel $w + wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]] + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + pack [label $w.msg \ + -justify left \ + -anchor w \ + -text [strcat \ + [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."] \ + "\n\n$err"] \ + ] -anchor w + + frame $w.buttons + button $w.buttons.continue \ + -text [mc "Continue"] \ + -command [list destroy $w] + pack $w.buttons.continue -side right -padx 5 + button $w.buttons.unlock \ + -text [mc "Unlock Index"] \ + -command "destroy $w; _delete_indexlock" + pack $w.buttons.unlock -side right + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + wm protocol $w WM_DELETE_WINDOW update + bind $w.buttons.continue <Visibility> " + grab $w + focus $w.buttons.continue + " + tkwait window $w + + $::main_status stop + unlock_index + rescan $after 0 + return + } + + $::main_status stop + unlock_index + uplevel #0 $after +} + proc update_indexinfo {msg pathList after} { global update_index_cp @@ -12,12 +62,7 @@ proc update_indexinfo {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - 0.0] + $::main_status start $msg [mc "files"] set fd [git_write update-index -z --index-info] fconfigure $fd \ -blocking 0 \ @@ -31,19 +76,16 @@ proc update_indexinfo {msg pathList after} { $pathList \ $totalCnt \ $batch \ - $msg \ $after \ ] } -proc write_update_indexinfo {fd pathList totalCnt batch msg after} { +proc write_update_indexinfo {fd pathList totalCnt batch after} { global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after + _close_updateindex $fd $after return } @@ -68,12 +110,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { display_file $path $new } - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] + $::main_status update $update_index_cp $totalCnt } proc update_index {msg pathList after} { @@ -87,12 +124,7 @@ proc update_index {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - 0.0] + $::main_status start $msg [mc "files"] set fd [git_write update-index --add --remove -z --stdin] fconfigure $fd \ -blocking 0 \ @@ -106,19 +138,16 @@ proc update_index {msg pathList after} { $pathList \ $totalCnt \ $batch \ - $msg \ $after \ ] } -proc write_update_index {fd pathList totalCnt batch msg after} { +proc write_update_index {fd pathList totalCnt batch after} { global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after + _close_updateindex $fd $after return } @@ -147,12 +176,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { display_file $path $new } - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] + $::main_status update $update_index_cp $totalCnt } proc checkout_index {msg pathList after} { @@ -166,12 +190,7 @@ proc checkout_index {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - 0.0] + $::main_status start $msg [mc "files"] set fd [git_write checkout-index \ --index \ --quiet \ @@ -191,19 +210,16 @@ proc checkout_index {msg pathList after} { $pathList \ $totalCnt \ $batch \ - $msg \ $after \ ] } -proc write_checkout_index {fd pathList totalCnt batch msg after} { +proc write_checkout_index {fd pathList totalCnt batch after} { global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after + _close_updateindex $fd $after return } @@ -222,12 +238,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { } } - ui_status [format \ - "%s... %i/%i files (%.2f%%)" \ - $msg \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] + $::main_status update $update_index_cp $totalCnt } proc unstage_helper {txt paths} { @@ -268,7 +279,7 @@ proc do_unstage_selection {} { [array names selected_paths] } elseif {$current_diff_path ne {}} { unstage_helper \ - "Unstaging [short_path $current_diff_path] from commit" \ + [mc "Unstaging %s from commit" [short_path $current_diff_path]] \ [list $current_diff_path] } } @@ -312,7 +323,7 @@ proc do_add_selection {} { [array names selected_paths] } elseif {$current_diff_path ne {}} { add_helper \ - "Adding [short_path $current_diff_path]" \ + [mc "Adding %s" [short_path $current_diff_path]] \ [list $current_diff_path] } } @@ -351,26 +362,35 @@ proc revert_helper {txt paths} { } } + + # Split question between singular and plural cases, because + # such distinction is needed in some languages. Previously, the + # code used "Revert changes in" for both, but that can't work + # in languages where 'in' must be combined with word from + # rest of string (in diffrent way for both cases of course). + # + # FIXME: Unfortunately, even that isn't enough in some languages + # as they have quite complex plural-form rules. Unfortunately, + # msgcat doesn't seem to support that kind of string translation. + # set n [llength $pathList] if {$n == 0} { unlock_index return } elseif {$n == 1} { - set s "[short_path [lindex $pathList]]" + set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]] } else { - set s "these $n files" + set query [mc "Revert changes in these %i files?" $n] } set reply [tk_dialog \ .confirm_revert \ "[appname] ([reponame])" \ - "Revert changes in $s? - -Any unstaged changes will be permanently lost by the revert." \ + [mc "Any unstaged changes will be permanently lost by the revert."] \ question \ 1 \ - {Do Nothing} \ - {Revert Changes} \ + [mc "Do Nothing"] \ + [mc "Revert Changes"] \ ] if {$reply == 1} { checkout_index \ diff --git a/git-gui/lib/logo.tcl b/git-gui/lib/logo.tcl new file mode 100644 index 0000000000..5ff76692f5 --- /dev/null +++ b/git-gui/lib/logo.tcl @@ -0,0 +1,43 @@ +# git-gui Git Gui logo +# Copyright (C) 2007 Shawn Pearce + +# Henrik Nyh's alternative Git logo, from his blog post +# http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon +# +image create photo ::git_logo_data -data { +R0lGODdhYQC8AIQbAGZmZtg4LW9vb3l5eYKCgoyMjEC/TOJpYZWVlZ+fn2/PeKmpqbKysry8vMXF +xZ/fpc/Pz7fnvPXNytnZ2eLi4s/v0vja1+zs7Of36fX19f3z8v///////////////////ywAAAAA +YQC8AAAF/uAmjmRpnmiqrmzrvq4hz3RtGw+s7zx5/7dcb0hUAY8zYXHJRCKVzGjPeYRKry8q0Irt +GrVBr3gFDo/PprKNix6ra+y2902Ly7H05L2dl9n3UX04gGeCf4RFhohiiotdjY5XkJGBfYeUOpOY +iZablXmXURgPpKWmp6ipqYIKqq6vqREjFYK1trUKs7e7vFq5IrS9wsM0vxvBxMm8xsjKzqy6z9J5 +zNPWatXX2k7Z29433d/iMuHj3+Xm2+jp1+vs0+7vz/HyyvT1xPf4wvr7y9H+pBkbBasgLFYGE8ba +o8nTlE4OOYGKKJFOKIopGmLMAnHjDo0eWYAM+WUiSRgj/k+eSKmyBMuWI17C3CATZs2WN1XmPLmT +ZM+QPz0G3VihqNGjSJNWwDCzqdOnUKPu0SChqtWrWLNq3cq1q9evYCVYGCEhgNmzaNOqXcu2rdu3 +cOMGOEBWrt27ePPCpSuirN6/gAO35bvBr+DDiPMSNpy4sWO2ix9Lnmw2MuXLiS1j3gxYM+fPdz2D +Hv1WNOnTak2jXj23LuvXlV3DZq16Nujatjnjzo15N2/Kvn9LDi7cMfHimaUqX868ufPn0KPPpOCA +AQMWCQBo3869u/fv4MNrd3DlQoMC3QlkSJFdvPv38LVDWJLBAYHwE1LE38+/+/UhGTAggHv5odDf +gfv9/seDgPAVeAKCELqnIAwU3BefgyZEqOF3E7rAQH8YlrDhiNt1uEIG6IGoH4kjmpjCBRaqaCCL +G7p4AgUDIhgiCTTW2AKOEe44Qo8a2khCBgNoKKQIREZopAgZxAjhkhs0CeGTG7Sn5IpW9vekAyRS +2eWBRl6Q44ZijhlfAQlQmeKIaarpHZsMTHABCxDQGKec3JH3QpIs7snndn6yAKaeXA7aZwuABppo +fAws0GiEhaKQJ40F3DkjfwVC8CaCAlCgAgIkJjDfCgdiOMGn/Q2w3gkZtPgqC6ma0ECECaBwa4QE +aOpCrSYAqeMJpEKYqw7ABnsmfwQ8aCwPySqLYKUb/kwAYbPQyoiCtQcOUMKHBwrgK7LaogBuuaxC +OkS0KEwa37EiLBufALPuwO4Jh/InwAixkknEvSe4C9+p3PY3rr3lpnDufguIcCmzRQAc7IHYLhxf +w/8mnILA74lg8cARa4xCsZxusMCBomZccgsfv0deuh2HvLKh/sLs3hJSvieuCwUzvIHN4tGXc3ih +vtDzmj8fSNLR8BWQdH9LH+g00OFF3d/UBx4cUcvuOc21eFRiouV+Xvvr0dDvlX21R/2uzTR89TqU +L3+5UoBgAxtRHd5/CHpLkd13i4D2e3hHRLKMY+9Hr0Nvx/fq3Pw57cng7/m9wQVObnIyhAiQwHF8 +/tQS8nDgI2wOYeh3CAvhuIBHiDEgqvdtwudkaz3GBPKaTcKuGgqAJRMZmK6h1hnk3ncDcUvhgPFS +o5B476ZKQcECzCN4qgmYN4lAncmzcAEEkhJp+QlfkyhAAdtbN8H67FvHQAF6b4g6v9UryqfkKkBu +v/0prxD//kR63YnqB8AeqcdoBRxU/1zAuwRaaX4reJ4DSSRAHUhwgrgqwgUx2B94EWGDHISPBzUY +QgSNcAn6K6F4fscDCtBOhdoRwPW6kIHDwZA7vWoDBF44Qd/tIUAEBCACbIeG4AXxfmFrQ4B4OCYE +JBEQELChmgbAACJioj4JOCKCCLCABZ6EAg1IHwDlyLYAB1gRJhSYgHUQAD9WnQ9+CWBAA+wknTpC +JwQAOw== +} + +proc git_logo {w} { + label $w \ + -borderwidth 1 \ + -relief sunken \ + -background white \ + -image ::git_logo_data + return $w +} diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 0e50919d4c..63e14279c1 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -10,10 +10,10 @@ method _can_merge {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { - info_popup {Cannot merge while amending. + info_popup [mc "Cannot merge while amending. You must finish amending this commit before starting any type of merge. -} +"] return 0 } @@ -24,12 +24,12 @@ You must finish amending this commit before starting any type of merge. # repository_state curType curHEAD curMERGE_HEAD if {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. + info_popup [mc "Last scanned state does not match repository state. Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. The rescan will be automatically started now. -} +"] unlock_index rescan ui_ready return 0 @@ -41,22 +41,22 @@ The rescan will be automatically started now. continue; # and pray it works! } U? { - error_popup "You are in the middle of a conflicted merge. + error_popup [mc "You are in the middle of a conflicted merge. -File [short_path $path] has merge conflicts. +File %s has merge conflicts. You must resolve them, stage the file, and commit to complete the current merge. Only then can you begin another merge. -" +" [short_path $path]] unlock_index return 0 } ?? { - error_popup "You are in the middle of a change. + error_popup [mc "You are in the middle of a change. -File [short_path $path] is modified. +File %s is modified. You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. -" +" [short_path $path]] unlock_index return 0 } @@ -103,7 +103,7 @@ method _start {} { regsub {^[^:@]*@} $remote {} remote } set branch [lindex $spec 2] - set stitle "$branch of $remote" + set stitle [mc "%s of %s" $branch $remote] } regsub ^refs/heads/ $branch {} branch puts $fh "$cmit\t\tbranch '$branch' of $remote" @@ -116,9 +116,9 @@ method _start {} { lappend cmd HEAD lappend cmd $name - set msg "Merging $current_branch and $stitle" + set msg [mc "Merging %s and %s" $current_branch $stitle] ui_status "$msg..." - set cons [console::new "Merge" "merge $stitle"] + set cons [console::new [mc "Merge"] "merge $stitle"] console::exec $cons $cmd [cb _finish $cons] wm protocol $w WM_DELETE_WINDOW {} @@ -128,9 +128,9 @@ method _start {} { method _finish {cons ok} { console::done $cons $ok if {$ok} { - set msg {Merge completed successfully.} + set msg [mc "Merge completed successfully."] } else { - set msg {Merge failed. Conflict resolution is required.} + set msg [mc "Merge failed. Conflict resolution is required."] } unlock_index rescan [list ui_status $msg] @@ -147,7 +147,7 @@ constructor dialog {} { } make_toplevel top w - wm title $top "[appname] ([reponame]): Merge" + wm title $top [append "[appname] ([reponame]): " [mc "Merge"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } @@ -155,26 +155,26 @@ constructor dialog {} { set _start [cb _start] label $w.header \ - -text "Merge Into $current_branch" \ + -text [mc "Merge Into %s" $current_branch] \ -font font_uibold pack $w.header -side top -fill x frame $w.buttons button $w.buttons.visualize \ - -text Visualize \ + -text [mc Visualize] \ -command [cb _visualize] pack $w.buttons.visualize -side left button $w.buttons.merge \ - -text Merge \ + -text [mc Merge] \ -command $_start pack $w.buttons.merge -side right button $w.buttons.cancel \ - -text {Cancel} \ + -text [mc "Cancel"] \ -command [cb _cancel] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - set w_rev [::choose_rev::new_unmerged $w.rev {Revision To Merge}] + set w_rev [::choose_rev::new_unmerged $w.rev [mc "Revision To Merge"]] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 bind $w <$M1B-Key-Return> $_start @@ -209,34 +209,34 @@ proc reset_hard {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { - info_popup {Cannot abort while amending. + info_popup [mc "Cannot abort while amending. You must finish amending this commit. -} +"] return } if {![lock_index abort]} return if {[string match *merge* $commit_type]} { - set op_question "Abort merge? + set op_question [mc "Abort merge? Aborting the current merge will cause *ALL* uncommitted changes to be lost. -Continue with aborting the current merge?" +Continue with aborting the current merge?"] } else { - set op_question "Reset changes? + set op_question [mc "Reset changes? Resetting the changes will cause *ALL* uncommitted changes to be lost. -Continue with resetting the current changes?" +Continue with resetting the current changes?"] } if {[ask_popup $op_question] eq {yes}} { set fd [git_read --stderr read-tree --reset -u -v HEAD] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [namespace code [list _reset_wait $fd]] - $::main_status start {Aborting} {files reset} + $::main_status start [mc "Aborting"] {files reset} } else { unlock_index } @@ -263,9 +263,9 @@ proc _reset_wait {fd} { catch {file delete [gitdir GITGUI_MSG]} if {$fail} { - warn_popup "Abort failed.\n\n$err" + warn_popup "[mc "Abort failed."]\n\n$err" } - rescan {ui_status {Abort completed. Ready.}} + rescan {ui_status [mc "Abort completed. Ready."]} } else { fconfigure $fd -blocking 0 } diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index 063f5df6f7..f812e5e89a 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -54,85 +54,6 @@ proc save_config {} { } } -proc do_about {} { - global appvers copyright oguilib - global tcl_patchLevel tk_patchLevel - - set w .about_dialog - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "About [appname]" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.close -text {Close} \ - -default active \ - -command [list destroy $w] - pack $w.buttons.close -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - label $w.desc \ - -text "git-gui - a graphical user interface for Git. -$copyright" \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.desc -side top -fill x -padx 5 -pady 5 - - set v {} - append v "git-gui version $appvers\n" - append v "[git version]\n" - append v "\n" - if {$tcl_patchLevel eq $tk_patchLevel} { - append v "Tcl/Tk version $tcl_patchLevel" - } else { - append v "Tcl version $tcl_patchLevel" - append v ", Tk version $tk_patchLevel" - } - - set d {} - append d "git wrapper: $::_git\n" - append d "git exec dir: [gitexec]\n" - append d "git-gui lib: $oguilib" - - label $w.vers \ - -text $v \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.vers -side top -fill x -padx 5 -pady 5 - - label $w.dirs \ - -text $d \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.dirs -side top -fill x -padx 5 -pady 5 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command \ - -label {Copy} \ - -command " - clipboard clear - clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] - " - - bind $w <Visibility> "grab $w; focus $w.buttons.close" - bind $w <Key-Escape> "destroy $w" - bind $w <Key-Return> "destroy $w" - bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" - wm title $w "About [appname]" - tkwait window $w -} - proc do_options {} { global repo_config global_config font_descs global repo_config_new global_config_new @@ -157,48 +78,44 @@ proc do_options {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text "Options" \ - -font font_uibold - pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.restore -text {Restore Defaults} \ + button $w.buttons.restore -text [mc "Restore Defaults"] \ -default normal \ -command do_restore_defaults pack $w.buttons.restore -side left - button $w.buttons.save -text Save \ + button $w.buttons.save -text [mc Save] \ -default active \ -command [list do_save_config $w] pack $w.buttons.save -side right - button $w.buttons.cancel -text {Cancel} \ + button $w.buttons.cancel -text [mc "Cancel"] \ -default normal \ -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.repo -text "[reponame] Repository" - labelframe $w.global -text {Global (All Repositories)} + labelframe $w.repo -text [mc "%s Repository" [reponame]] + labelframe $w.global -text [mc "Global (All Repositories)"] pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 set optid 0 foreach option { - {t user.name {User Name}} - {t user.email {Email Address}} - - {b merge.summary {Summarize Merge Commits}} - {i-1..5 merge.verbosity {Merge Verbosity}} - {b merge.diffstat {Show Diffstat After Merge}} - - {b gui.trustmtime {Trust File Modification Timestamps}} - {b gui.pruneduringfetch {Prune Tracking Branches During Fetch}} - {b gui.matchtrackingbranch {Match Tracking Branches}} - {i-0..99 gui.diffcontext {Number of Diff Context Lines}} - {t gui.newbranchtemplate {New Branch Name Template}} + {t user.name {mc "User Name"}} + {t user.email {mc "Email Address"}} + + {b merge.summary {mc "Summarize Merge Commits"}} + {i-1..5 merge.verbosity {mc "Merge Verbosity"}} + {b merge.diffstat {mc "Show Diffstat After Merge"}} + + {b gui.trustmtime {mc "Trust File Modification Timestamps"}} + {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}} + {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} + {i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}} + {t gui.newbranchtemplate {mc "New Branch Name Template"}} } { set type [lindex $option 0] set name [lindex $option 1] - set text [lindex $option 2] + set text [eval [lindex $option 2]] incr optid foreach f {repo global} { switch -glob -- $type { @@ -246,7 +163,7 @@ proc do_options {} { foreach option $font_descs { set name [lindex $option 0] set font [lindex $option 1] - set text [lindex $option 2] + set text [eval [lindex $option 2]] set global_config_new(gui.$font^^family) \ [font configure $font -family] @@ -278,7 +195,13 @@ proc do_options {} { bind $w <Visibility> "grab $w; focus $w.buttons.save" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> [list do_save_config $w] - wm title $w "[appname] ([reponame]): Options" + + if {[is_MacOSX]} { + set t [mc "Preferences"] + } else { + set t [mc "Options"] + } + wm title $w "[appname] ([reponame]): $t" tkwait window $w } @@ -309,7 +232,7 @@ proc do_restore_defaults {} { proc do_save_config {w} { if {[catch {save_config} err]} { - error_popup "Failed to completely save options:\n\n$err" + error_popup [strcat [mc "Failed to completely save options:"] "\n\n$err"] } reshow_diff destroy $w diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl index cf9b9d5829..0e86ddac09 100644 --- a/git-gui/lib/remote.tcl +++ b/git-gui/lib/remote.tcl @@ -135,8 +135,10 @@ proc load_all_remotes {} { proc populate_fetch_menu {} { global all_remotes repo_config - set m .mbar.fetch - set prune_list [list] + 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)}]} { @@ -157,28 +159,34 @@ proc populate_fetch_menu {} { } if {$enable} { - lappend prune_list $r - $m add command \ - -label "Fetch from $r..." \ + 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 + } + + $fetch_m add command \ + -label $r \ -command [list fetch_from $r] + $prune_m add command \ + -label $r \ + -command [list prune_from $r] } } - - if {$prune_list ne {}} { - $m add separator - } - foreach r $prune_list { - $m add command \ - -label "Prune from $r..." \ - -command [list prune_from $r] - } } proc populate_push_menu {} { global all_remotes repo_config - set m .mbar.push - set fast_count 0 + 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)}]} { @@ -199,13 +207,16 @@ proc populate_push_menu {} { } if {$enable} { - if {!$fast_count} { - $m add separator + if {![winfo exists $push_m]} { + menu $push_m + $remote_m insert 0 cascade \ + -label [mc "Push to"] \ + -menu $push_m } - $m add command \ - -label "Push to $r..." \ + + $push_m add command \ + -label $r \ -command [list push_to $r] - incr fast_count } } } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index c88a360db5..c7b8148698 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -26,28 +26,28 @@ constructor dialog {} { global all_remotes M1B make_toplevel top w - wm title $top "[appname] ([reponame]): Delete Remote Branch" + wm title $top [append "[appname] ([reponame]): " [mc "Delete Remote Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text {Delete Remote Branch} -font font_uibold + label $w.header -text [mc "Delete Remote Branch"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.delete -text Delete \ + button $w.buttons.delete -text [mc Delete] \ -default active \ -command [cb _delete] pack $w.buttons.delete -side right - button $w.buttons.cancel -text {Cancel} \ + 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.dest -text {From Repository} + labelframe $w.dest -text [mc "From Repository"] if {$all_remotes ne {}} { radiobutton $w.dest.remote_r \ - -text {Remote:} \ + -text [mc "Remote:"] \ -value remote \ -variable @urltype eval tk_optionMenu $w.dest.remote_m @remote $all_remotes @@ -63,7 +63,7 @@ constructor dialog {} { set urltype url } radiobutton $w.dest.url_r \ - -text {Arbitrary URL:} \ + -text [mc "Arbitrary URL:"] \ -value url \ -variable @urltype entry $w.dest.url_t \ @@ -81,7 +81,7 @@ constructor dialog {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.heads -text {Branches} + labelframe $w.heads -text [mc "Branches"] listbox $w.heads.l \ -height 10 \ -width 70 \ @@ -96,7 +96,7 @@ constructor dialog {} { -anchor w \ -justify left button $w.heads.footer.rescan \ - -text {Rescan} \ + -text [mc "Rescan"] \ -command [cb _rescan] pack $w.heads.footer.status -side left -fill x pack $w.heads.footer.rescan -side right @@ -106,9 +106,9 @@ constructor dialog {} { pack $w.heads.l -side left -fill both -expand 1 pack $w.heads -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.validate -text {Delete Only If} + labelframe $w.validate -text [mc "Delete Only If"] radiobutton $w.validate.head_r \ - -text {Merged Into:} \ + -text [mc "Merged Into:"] \ -value head \ -variable @checktype set head_m [tk_optionMenu $w.validate.head_m @check_head {}] @@ -116,7 +116,7 @@ constructor dialog {} { trace add variable @check_head write [cb _write_check_head] grid $w.validate.head_r $w.validate.head_m -sticky w radiobutton $w.validate.always_r \ - -text {Always (Do not perform merge checks)} \ + -text [mc "Always (Do not perform merge checks)"] \ -value always \ -variable @checktype grid $w.validate.always_r -columnspan 2 -sticky w @@ -149,7 +149,7 @@ method _delete {} { -type ok \ -title [wm title $w] \ -parent $w \ - -message "A branch is required for 'Merged Into'." + -message [mc "A branch is required for 'Merged Into'."] return } set crev $full_cache("$cache\nrefs/heads/$check_head") @@ -181,14 +181,12 @@ method _delete {} { } if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $check_head: + set msg [mc "The following branches are not completely merged into %s: - - [join $not_merged "\n - "]" + - %s" $check_head [join $not_merged "\n - "]] if {$need_fetch} { - append msg " - -One or more of the merge tests failed because you have not fetched the necessary commits. Try fetching from $uri first." + append msg "\n\n" [mc "One or more of the merge tests failed because you have not fetched the necessary commits. Try fetching from %s first." $uri] } tk_messageBox \ @@ -206,7 +204,7 @@ One or more of the merge tests failed because you have not fetched the necessary -type ok \ -title [wm title $w] \ -parent $w \ - -message "Please select one or more branches to delete." + -message [mc "Please select one or more branches to delete."] return } @@ -215,9 +213,9 @@ One or more of the merge tests failed because you have not fetched the necessary -type yesno \ -title [wm title $w] \ -parent $w \ - -message {Recovering deleted branches is difficult. + -message [mc "Recovering deleted branches is difficult. -Delete the selected branches?}] ne yes} { +Delete the selected branches?"]] ne yes} { return } @@ -225,7 +223,7 @@ Delete the selected branches?}] ne yes} { set cons [console::new \ "push $uri" \ - "Deleting branches from $uri"] + [mc "Deleting branches from %s" $uri]] console::exec $cons $push_cmd } @@ -285,12 +283,12 @@ method _load {cache uri} { $w.heads.l conf -state disabled set head_list [list] set full_list [list] - set status {No repository selected.} + set status [mc "No repository selected."] return } if {[catch {set x $cached($cache)}]} { - set status "Scanning $uri..." + set status [mc "Scanning %s..." $uri] $w.heads.l conf -state disabled set head_list [list] set full_list [list] diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index c36be2f3cd..38c3151b05 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -2,28 +2,22 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc do_windows_shortcut {} { - global argv0 - set fn [tk_getSaveFile \ -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialfile "Git [reponame].bat"] + -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ + -initialfile "Git [reponame].lnk"] if {$fn != {}} { - if {[file extension $fn] ne {.bat}} { - set fn ${fn}.bat + if {[file extension $fn] ne {.lnk}} { + set fn ${fn}.lnk } if {[catch { - set ge [file normalize [file dirname $::_git]] - set fd [open $fn w] - puts $fd "@ECHO Entering [reponame]" - puts $fd "@ECHO Starting git-gui... please wait..." - puts $fd "@SET PATH=$ge;%PATH%" - puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" - puts -nonewline $fd "@\"[info nameofexecutable]\"" - puts $fd " \"[file normalize $argv0]\"" - close $fd + win32_create_lnk $fn [list \ + [info nameofexecutable] \ + [file normalize $::argv0] \ + ] \ + [file dirname [file normalize [gitdir]]] } err]} { - error_popup "Cannot write script:\n\n$err" + error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } } } @@ -42,15 +36,14 @@ proc do_cygwin_shortcut {} { } set fn [tk_getSaveFile \ -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ + -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ -initialdir $desktop \ - -initialfile "Git [reponame].bat"] + -initialfile "Git [reponame].lnk"] if {$fn != {}} { - if {[file extension $fn] ne {.bat}} { - set fn ${fn}.bat + if {[file extension $fn] ne {.lnk}} { + set fn ${fn}.lnk } if {[catch { - set fd [open $fn w] set sh [exec cygpath \ --windows \ --absolute \ @@ -59,19 +52,13 @@ proc do_cygwin_shortcut {} { --unix \ --absolute \ $argv0] - set gd [exec cygpath \ - --unix \ - --absolute \ - [gitdir]] - puts $fd "@ECHO Entering [reponame]" - puts $fd "@ECHO Starting git-gui... please wait..." - puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR=[sq $gd]" - puts -nonewline $fd " [sq $me]" - puts $fd " &\"" - close $fd + win32_create_lnk $fn [list \ + $sh -c \ + "CHERE_INVOKING=1 source /etc/profile;[sq $me]" \ + ] \ + [file dirname [file normalize [gitdir]]] } err]} { - error_popup "Cannot write script:\n\n$err" + error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } } } @@ -81,7 +68,7 @@ proc do_macosx_app {} { set fn [tk_getSaveFile \ -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ + -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ -initialdir [file join $env(HOME) Desktop] \ -initialfile "Git [reponame].app"] if {$fn != {}} { @@ -146,7 +133,7 @@ proc do_macosx_app {} { file attributes $exe -permissions u+x,g+x,o+x } err]} { - error_popup "Cannot write icon:\n\n$err" + error_popup [strcat [mc "Cannot write icon:"] "\n\n$err"] } } } diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl index 3bf79eb6e0..51d4177551 100644 --- a/git-gui/lib/status_bar.tcl +++ b/git-gui/lib/status_bar.tcl @@ -6,6 +6,7 @@ class status_bar { field w ; # our own window path field w_l ; # text widget we draw messages into field w_c ; # canvas we draw a progress bar into +field c_pack ; # script to pack the canvas with field status {}; # single line of text we show field prefix {}; # text we format into status field units {}; # unit of progress @@ -24,6 +25,29 @@ constructor new {path} { -anchor w \ -justify left pack $w_l -side left + set c_pack [cb _oneline_pack] + + bind $w <Destroy> [cb _delete %W] + return $this +} + +method _oneline_pack {} { + $w_c conf -width 100 + pack $w_c -side right +} + +constructor two_line {path} { + set w $path + set w_l $w.l + set w_c $w.c + + frame $w + label $w_l \ + -textvariable @status \ + -anchor w \ + -justify left + pack $w_l -anchor w -fill x + set c_pack [list pack $w_c -fill x] bind $w <Destroy> [cb _delete %W] return $this @@ -34,13 +58,12 @@ method start {msg uds} { $w_c coords bar 0 0 0 20 } else { canvas $w_c \ - -width 100 \ -height [expr {int([winfo reqheight $w_l] * 0.6)}] \ -borderwidth 1 \ -relief groove \ -highlightt 0 $w_c create rectangle 0 0 0 20 -tags bar -fill navy - pack $w_c -side right + eval $c_pack } set status $msg @@ -53,11 +76,16 @@ method update {have total} { set pdone 0 if {$total > 0} { set pdone [expr {100 * $have / $total}] + set cdone [expr {[winfo width $w_c] * $have / $total}] } - set status [format "%s ... %i of %i %s (%2i%%)" \ - $prefix $have $total $units $pdone] - $w_c coords bar 0 0 $pdone 20 + set prec [string length [format %i $total]] + set status [mc "%s ... %*i of %*i %s (%3i%%)" \ + $prefix \ + $prec $have \ + $prec $total \ + $units $pdone] + $w_c coords bar 0 0 $cdone 20 } method update_meter {buf} { diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index 3a22bd40d4..8e6a9d0a60 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -3,8 +3,8 @@ proc fetch_from {remote} { set w [console::new \ - "fetch $remote" \ - "Fetching new changes from $remote"] + [mc "fetch %s" $remote] \ + [mc "Fetching new changes from %s" $remote]] set cmds [list] lappend cmds [list exec git fetch $remote] if {[is_config_true gui.pruneduringfetch]} { @@ -15,15 +15,15 @@ proc fetch_from {remote} { proc prune_from {remote} { set w [console::new \ - "remote prune $remote" \ - "Pruning tracking branches deleted from $remote"] + [mc "remote prune %s" $remote] \ + [mc "Pruning tracking branches deleted from %s" $remote]] console::exec $w [list git remote prune $remote] } proc push_to {remote} { set w [console::new \ - "push $remote" \ - "Pushing changes to $remote"] + [mc "push %s" $remote] \ + [mc "Pushing changes to %s" $remote]] set cmd [list git push] lappend cmd -v lappend cmd $remote @@ -32,6 +32,7 @@ proc push_to {remote} { proc start_push_anywhere_action {w} { global push_urltype push_remote push_url push_thin push_tags + global push_force set r_url {} switch -- $push_urltype { @@ -45,6 +46,9 @@ proc start_push_anywhere_action {w} { if {$push_thin} { lappend cmd --thin } + if {$push_force} { + lappend cmd --force + } if {$push_tags} { lappend cmd --tags } @@ -64,8 +68,8 @@ proc start_push_anywhere_action {w} { } set cons [console::new \ - "push $r_url" \ - "Pushing $cnt $unit to $r_url"] + [mc "push %s" $r_url] \ + [mc "Pushing %s %s to %s" $cnt $unit $r_url]] console::exec $cons $cmd destroy $w } @@ -76,26 +80,27 @@ trace add variable push_remote write \ proc do_push_anywhere {} { global all_remotes current_branch global push_urltype push_remote push_url push_thin push_tags + global push_force set w .push_setup toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text {Push Branches} -font font_uibold + label $w.header -text [mc "Push Branches"] -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.create -text Push \ + button $w.buttons.create -text [mc Push] \ -default active \ -command [list start_push_anywhere_action $w] pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ + button $w.buttons.cancel -text [mc "Cancel"] \ -default normal \ -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.source -text {Source Branches} + labelframe $w.source -text [mc "Source Branches"] listbox $w.source.l \ -height 10 \ -width 70 \ @@ -112,10 +117,10 @@ proc do_push_anywhere {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.dest -text {Destination Repository} + labelframe $w.dest -text [mc "Destination Repository"] if {$all_remotes ne {}} { radiobutton $w.dest.remote_r \ - -text {Remote:} \ + -text [mc "Remote:"] \ -value remote \ -variable push_urltype eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes @@ -130,7 +135,7 @@ proc do_push_anywhere {} { set push_urltype url } radiobutton $w.dest.url_r \ - -text {Arbitrary URL:} \ + -text [mc "Arbitrary URL:"] \ -value url \ -variable push_urltype entry $w.dest.url_t \ @@ -150,25 +155,30 @@ proc do_push_anywhere {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.options -text {Transfer Options} + labelframe $w.options -text [mc "Transfer Options"] + checkbutton $w.options.force \ + -text [mc "Force overwrite existing branch (may discard changes)"] \ + -variable push_force + grid $w.options.force -columnspan 2 -sticky w checkbutton $w.options.thin \ - -text {Use thin pack (for slow network connections)} \ + -text [mc "Use thin pack (for slow network connections)"] \ -variable push_thin grid $w.options.thin -columnspan 2 -sticky w checkbutton $w.options.tags \ - -text {Include tags} \ + -text [mc "Include tags"] \ -variable push_tags grid $w.options.tags -columnspan 2 -sticky w grid columnconfigure $w.options 1 -weight 1 pack $w.options -anchor nw -fill x -pady 5 -padx 5 set push_url {} + set push_force 0 set push_thin 0 set push_tags 0 bind $w <Visibility> "grab $w; focus $w.buttons.create" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> [list start_push_anywhere_action $w] - wm title $w "[appname] ([reponame]): Push" + wm title $w [append "[appname] ([reponame]): " [mc "Push"]] tkwait window $w } diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl new file mode 100644 index 0000000000..d7f93d045d --- /dev/null +++ b/git-gui/lib/win32.tcl @@ -0,0 +1,26 @@ +# git-gui Misc. native Windows 32 support +# Copyright (C) 2007 Shawn Pearce + +proc win32_read_lnk {lnk_path} { + return [exec cscript.exe \ + /E:jscript \ + /nologo \ + [file join $::oguilib win32_shortcut.js] \ + $lnk_path] +} + +proc win32_create_lnk {lnk_path lnk_exec lnk_dir} { + global oguilib + + set lnk_args [lrange $lnk_exec 1 end] + set lnk_exec [lindex $lnk_exec 0] + + eval [list exec wscript.exe \ + /E:jscript \ + /nologo \ + [file join $oguilib win32_shortcut.js] \ + $lnk_path \ + [file join $oguilib git-gui.ico] \ + $lnk_dir \ + $lnk_exec] $lnk_args +} diff --git a/git-gui/lib/win32_shortcut.js b/git-gui/lib/win32_shortcut.js new file mode 100644 index 0000000000..117923f886 --- /dev/null +++ b/git-gui/lib/win32_shortcut.js @@ -0,0 +1,34 @@ +// git-gui Windows shortcut support +// Copyright (C) 2007 Shawn Pearce + +var WshShell = WScript.CreateObject("WScript.Shell"); +var argv = WScript.Arguments; +var argi = 0; +var lnk_path = argv.item(argi++); +var ico_path = argi < argv.length ? argv.item(argi++) : undefined; +var dir_path = argi < argv.length ? argv.item(argi++) : undefined; +var lnk_exec = argi < argv.length ? argv.item(argi++) : undefined; +var lnk_args = ''; +while (argi < argv.length) { + var s = argv.item(argi++); + if (lnk_args != '') + lnk_args += ' '; + if (s.indexOf(' ') >= 0) { + lnk_args += '"'; + lnk_args += s; + lnk_args += '"'; + } else { + lnk_args += s; + } +} + +var lnk = WshShell.CreateShortcut(lnk_path); +if (argv.length == 1) { + WScript.echo(lnk.TargetPath); +} else { + lnk.TargetPath = lnk_exec; + lnk.Arguments = lnk_args; + lnk.IconLocation = ico_path + ", 0"; + lnk.WorkingDirectory = dir_path; + lnk.Save(); +} diff --git a/git-gui/macosx/AppMain.tcl b/git-gui/macosx/AppMain.tcl new file mode 100644 index 0000000000..41ca08e2b7 --- /dev/null +++ b/git-gui/macosx/AppMain.tcl @@ -0,0 +1,22 @@ +set gitexecdir {@@gitexecdir@@} +set gitguilib {@@GITGUI_LIBDIR@@} +set env(PATH) "$gitexecdir:$env(PATH)" + +if {[string first -psn [lindex $argv 0]] == 0} { + lset argv 0 [file join $gitexecdir git-gui] +} + +if {[file tail [lindex $argv 0]] eq {gitk}} { + set argv0 [file join $gitexecdir gitk] + set AppMain_source $argv0 +} else { + set argv0 [file join $gitexecdir [file tail [lindex $argv 0]]] + set AppMain_source [file join $gitguilib git-gui.tcl] + if {[pwd] eq {/}} { + cd $env(HOME) + } +} + +unset gitexecdir gitguilib +set argv [lrange $argv 1 end] +source $AppMain_source diff --git a/git-gui/macosx/Info.plist b/git-gui/macosx/Info.plist new file mode 100644 index 0000000000..99913ec57a --- /dev/null +++ b/git-gui/macosx/Info.plist @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>Wish</string> + <key>CFBundleGetInfoString</key> + <string>Git Gui @@GITGUI_VERSION@@ © 2006-2007 Shawn Pearce, et. al.</string> + <key>CFBundleIconFile</key> + <string>git-gui.icns</string> + <key>CFBundleIdentifier</key> + <string>cz.or.repo.git-gui</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>Git Gui</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>@@GITGUI_VERSION@@</string> + <key>CFBundleSignature</key> + <string>GITg</string> + <key>CFBundleVersion</key> + <string>@@GITGUI_VERSION@@</string> +</dict> +</plist> diff --git a/git-gui/macosx/git-gui.icns b/git-gui/macosx/git-gui.icns Binary files differnew file mode 100644 index 0000000000..77d88a77a7 --- /dev/null +++ b/git-gui/macosx/git-gui.icns diff --git a/git-gui/po/.gitignore b/git-gui/po/.gitignore new file mode 100644 index 0000000000..a89cf44969 --- /dev/null +++ b/git-gui/po/.gitignore @@ -0,0 +1,2 @@ +*.msg +*~ diff --git a/git-gui/po/README b/git-gui/po/README new file mode 100644 index 0000000000..9d8b7364fd --- /dev/null +++ b/git-gui/po/README @@ -0,0 +1,209 @@ +Localizing git-gui for your language +==================================== + +This short note is to help you, who reads and writes English and your +own language, help us getting git-gui localized for more languages. It +does not try to be a comprehensive manual of GNU gettext, which is the +i18n framework we use, but tries to help you get started by covering the +basics and how it is used in this project. + +1. Getting started. + +You would first need to have a working "git". Your distribution may +have it as "git-core" package (do not get "GNU Interactive Tools" -- +that is a different "git"). You would also need GNU gettext toolchain +to test the resulting translation out. Although you can work on message +translation files with a regular text editor, it is a good idea to have +specialized so-called "po file editors" (e.g. emacs po-mode, KBabel, +poedit, GTranslator --- any of them would work well). Please install +them. + +You would then need to clone the git-gui internationalization project +repository, so that you can work on it: + + $ git clone mob@repo.or.cz:/srv/git/git-gui/git-gui-i18n.git/ + $ cd git-gui-i18n + $ git checkout --track -b mob origin/mob + $ git config remote.origin.push mob + +The "git checkout" command creates a 'mob' branch from upstream's +corresponding branch and makes it your current branch. You will be +working on this branch. + +The "git config" command records in your repository configuration file +that you would push "mob" branch to the upstream when you say "git +push". + + +2. Starting a new language. + +In the git-gui-i18n directory is a po/ subdirectory. It has a +handful files whose names end with ".po". Is there a file that has +messages in your language? + +If you do not know what your language should be named, you need to find +it. This currently follows ISO 639-1 two letter codes: + + http://www.loc.gov/standards/iso639-2/php/code_list.php + +For example, if you are preparing a translation for Afrikaans, the +language code is "af". If there already is a translation for your +language, you do not have to perform any step in this section, but keep +reading, because we are covering the basics. + +If you did not find your language, you would need to start one yourself. +Copy po/git-gui.pot file to po/af.po (replace "af" with the code for +your language). Edit the first several lines to match existing *.po +files to make it clear this is a translation table for git-gui project, +and you are the primary translator. The result of your editing would +look something like this: + + # Translation of git-gui to Afrikaans + # Copyright (C) 2007 Shawn Pearce + # This file is distributed under the same license as the git-gui package. + # YOUR NAME <YOUR@E-MAIL.ADDRESS>, 2007. + # + #, fuzzy + msgid "" + msgstr "" + "Project-Id-Version: git-gui\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2007-07-24 22:19+0300\n" + "PO-Revision-Date: 2007-07-25 18:00+0900\n" + "Last-Translator: YOUR NAME <YOUR@E-MAIL.ADDRESS>\n" + "Language-Team: Afrikaans\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +You will find many pairs of a "msgid" line followed by a "msgstr" line. +These pairs define how messages in git-gui application are translated to +your language. Your primarily job is to fill in the empty double quote +pairs on msgstr lines with the translation of the strings on their +matching msgid lines. A few tips: + + - Control characters, such as newlines, are written in backslash + sequence similar to string literals in the C programming language. + When the string given on a msgid line has such a backslash sequence, + you would typically want to have corresponding ones in the string on + your msgstr line. + + - Some messages contain an optional context indicator at the end, + for example "@@noun" or "@@verb". This indicator allows the + software to select the correct translation depending upon the use. + The indicator is not actually part of the message and will not + be shown to the end-user. + + If your language does not require a different translation you + will still need to translate both messages. + + - Often the messages being translated are format strings given to + "printf()"-like functions. Make sure "%s", "%d", and "%%" in your + translated messages match the original. + + When you have to change the order of words, you can add "<number>\$" + between '%' and the conversion ('s', 'd', etc.) to say "<number>-th + parameter to the format string is used at this point". For example, + if the original message is like this: + + "Length is %d, Weight is %d" + + and if for whatever reason your translation needs to say weight first + and then length, you can say something like: + + "WEIGHT IS %2\$d, LENGTH IS %1\$d" + + The reason you need a backslash before dollar sign is because + this is a double quoted string in Tcl language, and without + it the letter introduces a variable interpolation, which you + do not want here. + + - A long message can be split across multiple lines by ending the + string with a double quote, and starting another string on the next + line with another double quote. They will be concatenated in the + result. For example: + + #: lib/remote_branch_delete.tcl:189 + #, tcl-format + msgid "" + "One or more of the merge tests failed because you have not fetched the " + "necessary commits. Try fetching from %s first." + msgstr "" + "HERE YOU WILL WRITE YOUR TRANSLATION OF THE ABOVE LONG " + "MESSAGE IN YOUR LANGUAGE." + +You can test your translation by running "make install", which would +create po/af.msg file and installs the result, and then running the +resulting git-gui under your locale: + + $ make install + $ LANG=af git-gui + +There is a trick to test your translation without first installing: + + $ make + $ LANG=af ./git-gui.sh + +When you are satisfied with your translation, commit your changes, and +push it back to the 'mob' branch: + + $ edit po/af.po + ... be sure to update Last-Translator: and + ... PO-Revision-Date: lines. + $ git add po/af.po + $ git commit -m 'Started Afrikaans translation.' + $ git push + + +3. Updating your translation. + +There may already be a translation for your language, and you may want +to contribute an update. This may be because you would want to improve +the translation of existing messages, or because the git-gui software +itself was updated and there are new messages that need translation. + +In any case, make sure you are up-to-date before starting your work: + + $ git pull + +In the former case, you will edit po/af.po (again, replace "af" with +your language code), and after testing and updating the Last-Translator: +and PO-Revision-Date: lines, "add/commit/push" as in the previous +section. + +By comparing "POT-Creation-Date:" line in po/git-gui.pot file and +po/af.po file, you can tell if there are new messages that need to be +translated. You would need the GNU gettext package to perform this +step. + + $ msgmerge -U po/af.po po/git-gui.pot + +[NEEDSWORK: who is responsible for updating po/git-gui.pot file by +running xgettext? IIRC, Christian recommended against running it +nilly-willy because it can become a source of unnecessary merge +conflicts. Perhaps we should mention something like " + +The po/git-gui.pot file is updated by the internationalization +coordinator from time to time. You _could_ update it yourself, but +translators are discouraged from doing so because we would want all +language teams to be working off of the same version of git-gui.pot. + +" here?] + +This updates po/af.po (again, replace "af" with your language +code) so that it contains msgid lines (i.e. the original) that +your translation did not have before. There are a few things to +watch out for: + + - The original text in English of an older message you already + translated might have been changed. You will notice a comment line + that begins with "#, fuzzy" in front of such a message. msgmerge + tool made its best effort to match your old translation with the + message from the updated software, but you may find cases that it + matched your old translated message to a new msgid and the pairing + does not make any sense -- you would need to fix them, and then + remove the "#, fuzzy" line from the message (your fixed translation + of the message will not be used before you remove the marker). + + - New messages added to the software will have msgstr lines with empty + strings. You would need to translate them. diff --git a/git-gui/po/de.po b/git-gui/po/de.po new file mode 100644 index 0000000000..3df30edb87 --- /dev/null +++ b/git-gui/po/de.po @@ -0,0 +1,1878 @@ +# Translation of git-gui to German. +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git package. +# Christian Stimming <stimming@tuhh.de>, 2007 +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-19 21:10+0200\n" +"PO-Revision-Date: 2007-10-20 15:28+0200\n" +"Last-Translator: Christian Stimming <stimming@tuhh.de>\n" +"Language-Team: German\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:590 git-gui.sh:604 git-gui.sh:617 git-gui.sh:700 +#: git-gui.sh:719 +msgid "git-gui: fatal error" +msgstr "git-gui: Programmfehler" + +#: git-gui.sh:551 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Ungültige Zeichensatz-Angabe in %s:" + +#: git-gui.sh:576 +msgid "Main Font" +msgstr "Programmschriftart" + +#: git-gui.sh:577 +msgid "Diff/Console Font" +msgstr "Vergleich-Schriftart" + +#: git-gui.sh:591 +msgid "Cannot find git in PATH." +msgstr "Git kann im PATH nicht gefunden werden." + +#: git-gui.sh:618 +msgid "Cannot parse Git version string:" +msgstr "Git Versionsangabe kann nicht erkannt werden:" + +#: git-gui.sh:636 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Die Version von Git kann nicht bestimmt werden.\n" +"\n" +"»%s« behauptet, es sei Version »%s«.\n" +"\n" +"%s benötigt mindestens Git 1.5.0 oder höher.\n" +"\n" +"Soll angenommen werden, »%s« sei Version 1.5.0?\n" + +#: git-gui.sh:874 +msgid "Git directory not found:" +msgstr "Git-Verzeichnis nicht gefunden:" + +#: git-gui.sh:881 +msgid "Cannot move to top of working directory:" +msgstr "" +"Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt " +"werden:" + +#: git-gui.sh:888 +msgid "Cannot use funny .git directory:" +msgstr "Unerwartete Struktur des .git Verzeichnis:" + +#: git-gui.sh:893 +msgid "No working directory" +msgstr "Kein Arbeitsverzeichnis" + +#: git-gui.sh:1040 +msgid "Refreshing file status..." +msgstr "Dateistatus aktualisieren..." + +#: git-gui.sh:1105 +msgid "Scanning for modified files ..." +msgstr "Nach geänderten Dateien suchen..." + +#: git-gui.sh:1280 lib/browser.tcl:245 +msgid "Ready." +msgstr "Bereit." + +#: git-gui.sh:1546 +msgid "Unmodified" +msgstr "Unverändert" + +#: git-gui.sh:1548 +msgid "Modified, not staged" +msgstr "Verändert, nicht bereitgestellt" + +#: git-gui.sh:1549 git-gui.sh:1554 +msgid "Staged for commit" +msgstr "Bereitgestellt zum Eintragen" + +#: git-gui.sh:1550 git-gui.sh:1555 +msgid "Portions staged for commit" +msgstr "Teilweise bereitgestellt zum Eintragen" + +#: git-gui.sh:1551 git-gui.sh:1556 +msgid "Staged for commit, missing" +msgstr "Bereitgestellt zum Eintragen, fehlend" + +#: git-gui.sh:1553 +msgid "Untracked, not staged" +msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt" + +#: git-gui.sh:1558 +msgid "Missing" +msgstr "Fehlend" + +#: git-gui.sh:1559 +msgid "Staged for removal" +msgstr "Bereitgestellt zum Löschen" + +#: git-gui.sh:1560 +msgid "Staged for removal, still present" +msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden" + +#: git-gui.sh:1562 git-gui.sh:1563 git-gui.sh:1564 git-gui.sh:1565 +msgid "Requires merge resolution" +msgstr "Konfliktauflösung nötig" + +#: git-gui.sh:1600 +msgid "Starting gitk... please wait..." +msgstr "Gitk wird gestartet... bitte warten." + +#: git-gui.sh:1609 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"Gitk kann nicht gestartet werden:\n" +"\n" +"%s existiert nicht" + +#: git-gui.sh:1809 lib/choose_repository.tcl:35 +msgid "Repository" +msgstr "Projektarchiv" + +#: git-gui.sh:1810 +msgid "Edit" +msgstr "Bearbeiten" + +#: git-gui.sh:1812 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "Zweig" + +#: git-gui.sh:1815 lib/choose_rev.tcl:547 +msgid "Commit@@noun" +msgstr "Version" + +#: git-gui.sh:1818 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "Zusammenführen" + +#: git-gui.sh:1819 lib/choose_rev.tcl:556 +msgid "Remote" +msgstr "Andere Archive" + +#: git-gui.sh:1828 +msgid "Browse Current Branch's Files" +msgstr "Aktuellen Zweig durchblättern" + +#: git-gui.sh:1832 +msgid "Browse Branch Files..." +msgstr "Einen Zweig durchblättern..." + +#: git-gui.sh:1837 +msgid "Visualize Current Branch's History" +msgstr "Aktuellen Zweig darstellen" + +#: git-gui.sh:1841 +msgid "Visualize All Branch History" +msgstr "Alle Zweige darstellen" + +#: git-gui.sh:1848 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Zweig »%s« durchblättern" + +#: git-gui.sh:1850 +#, tcl-format +msgid "Visualize %s's History" +msgstr "Historie von »%s« darstellen" + +#: git-gui.sh:1855 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Datenbankstatistik" + +#: git-gui.sh:1858 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Datenbank komprimieren" + +#: git-gui.sh:1861 +msgid "Verify Database" +msgstr "Datenbank überprüfen" + +#: git-gui.sh:1868 git-gui.sh:1872 git-gui.sh:1876 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +msgid "Create Desktop Icon" +msgstr "Desktop-Icon erstellen" + +#: git-gui.sh:1881 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184 +msgid "Quit" +msgstr "Beenden" + +#: git-gui.sh:1888 +msgid "Undo" +msgstr "Rückgängig" + +#: git-gui.sh:1891 +msgid "Redo" +msgstr "Wiederholen" + +#: git-gui.sh:1895 git-gui.sh:2388 +msgid "Cut" +msgstr "Ausschneiden" + +#: git-gui.sh:1898 git-gui.sh:2391 git-gui.sh:2462 git-gui.sh:2534 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "Kopieren" + +#: git-gui.sh:1901 git-gui.sh:2394 +msgid "Paste" +msgstr "Einfügen" + +#: git-gui.sh:1904 git-gui.sh:2397 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Löschen" + +#: git-gui.sh:1908 git-gui.sh:2401 git-gui.sh:2538 lib/console.tcl:69 +msgid "Select All" +msgstr "Alle auswählen" + +#: git-gui.sh:1917 +msgid "Create..." +msgstr "Erstellen..." + +#: git-gui.sh:1923 +msgid "Checkout..." +msgstr "Umstellen..." + +#: git-gui.sh:1929 +msgid "Rename..." +msgstr "Umbenennen..." + +#: git-gui.sh:1934 git-gui.sh:2033 +msgid "Delete..." +msgstr "Löschen..." + +#: git-gui.sh:1939 +msgid "Reset..." +msgstr "Zurücksetzen..." + +#: git-gui.sh:1951 git-gui.sh:2335 +msgid "New Commit" +msgstr "Neue Version" + +#: git-gui.sh:1959 git-gui.sh:2342 +msgid "Amend Last Commit" +msgstr "Letzte Version nachbessern" + +#: git-gui.sh:1968 git-gui.sh:2302 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Neu laden" + +#: git-gui.sh:1974 +msgid "Stage To Commit" +msgstr "Zum Eintragen bereitstellen" + +#: git-gui.sh:1979 +msgid "Stage Changed Files To Commit" +msgstr "Geänderte Dateien zum Eintragen bereitstellen" + +#: git-gui.sh:1985 +msgid "Unstage From Commit" +msgstr "Aus der Bereitstellung herausnehmen" + +#: git-gui.sh:1990 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "Änderungen revidieren" + +#: git-gui.sh:1997 git-gui.sh:2314 git-gui.sh:2412 +msgid "Sign Off" +msgstr "Abzeichnen" + +#: git-gui.sh:2001 git-gui.sh:2318 +msgid "Commit@@verb" +msgstr "Eintragen" + +#: git-gui.sh:2012 +msgid "Local Merge..." +msgstr "Lokales Zusammenführen..." + +#: git-gui.sh:2017 +msgid "Abort Merge..." +msgstr "Zusammenführen abbrechen..." + +#: git-gui.sh:2029 +msgid "Push..." +msgstr "Versenden..." + +#: git-gui.sh:2040 lib/choose_repository.tcl:40 +msgid "Apple" +msgstr "Apple" + +#: git-gui.sh:2043 git-gui.sh:2065 lib/about.tcl:13 +#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49 +#, tcl-format +msgid "About %s" +msgstr "Ãœber %s" + +#: git-gui.sh:2047 +msgid "Preferences..." +msgstr "Einstellungen..." + +#: git-gui.sh:2055 git-gui.sh:2580 +msgid "Options..." +msgstr "Optionen..." + +#: git-gui.sh:2061 lib/choose_repository.tcl:46 +msgid "Help" +msgstr "Hilfe" + +#: git-gui.sh:2102 +msgid "Online Documentation" +msgstr "Online-Dokumentation" + +#: git-gui.sh:2186 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" + +#: git-gui.sh:2219 +msgid "Current Branch:" +msgstr "Aktueller Zweig:" + +#: git-gui.sh:2240 +msgid "Staged Changes (Will Commit)" +msgstr "Bereitgestellte Änderungen (zum Eintragen)" + +#: git-gui.sh:2259 +msgid "Unstaged Changes" +msgstr "Nicht bereitgestellte Änderungen" + +#: git-gui.sh:2308 +msgid "Stage Changed" +msgstr "Alles bereitstellen" + +#: git-gui.sh:2324 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Versenden" + +#: git-gui.sh:2354 +msgid "Initial Commit Message:" +msgstr "Erste Versionsbeschreibung:" + +#: git-gui.sh:2355 +msgid "Amended Commit Message:" +msgstr "Nachgebesserte Versionsbeschreibung:" + +#: git-gui.sh:2356 +msgid "Amended Initial Commit Message:" +msgstr "Nachgebesserte erste Versionsbeschreibung:" + +#: git-gui.sh:2357 +msgid "Amended Merge Commit Message:" +msgstr "Nachgebesserte Zusammenführungs-Versionsbeschreibung:" + +#: git-gui.sh:2358 +msgid "Merge Commit Message:" +msgstr "Zusammenführungs-Versionsbeschreibung:" + +#: git-gui.sh:2359 +msgid "Commit Message:" +msgstr "Versionsbeschreibung:" + +#: git-gui.sh:2404 git-gui.sh:2542 lib/console.tcl:71 +msgid "Copy All" +msgstr "Alle kopieren" + +#: git-gui.sh:2428 lib/blame.tcl:104 +msgid "File:" +msgstr "Datei:" + +#: git-gui.sh:2530 +msgid "Refresh" +msgstr "Aktualisieren" + +#: git-gui.sh:2551 +msgid "Apply/Reverse Hunk" +msgstr "Änderung anwenden/umkehren" + +#: git-gui.sh:2557 +msgid "Decrease Font Size" +msgstr "Schriftgröße verkleinern" + +#: git-gui.sh:2561 +msgid "Increase Font Size" +msgstr "Schriftgröße vergrößern" + +#: git-gui.sh:2566 +msgid "Show Less Context" +msgstr "Weniger Kontext anzeigen" + +#: git-gui.sh:2573 +msgid "Show More Context" +msgstr "Mehr Kontext anzeigen" + +#: git-gui.sh:2587 +msgid "Unstage Hunk From Commit" +msgstr "Aus der Bereitstellung herausnehmen" + +#: git-gui.sh:2589 +msgid "Stage Hunk For Commit" +msgstr "In die Bereitstellung hinzufügen" + +#: git-gui.sh:2608 +msgid "Initializing..." +msgstr "Initialisieren..." + +#: git-gui.sh:2699 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" + +#: git-gui.sh:2729 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" + +#: git-gui.sh:2734 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - eine grafische Oberfläche für Git." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "Datei-Browser" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Version:" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "Version kopieren" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "%s lesen..." + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "" + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "" + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "" + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "Annotierung laden..." + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "Autor:" + +#: lib/blame.tcl:791 +msgid "Committer:" +msgstr "Eintragender:" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "Ursprüngliche Datei:" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "Ursprünglich von:" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "In Datei:" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "Kopiert oder verschoben durch:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Zweig umstellen" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Umstellen" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Abbrechen" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "Version" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "Optionen" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Ãœbernahmezweig anfordern" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Verbindung zu lokalem Zweig lösen" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Zweig erstellen" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Neuen Zweig erstellen" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375 +msgid "Create" +msgstr "Erstellen" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Zweigname" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Name:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "Passend zu Ãœbernahmezweig-Name" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Anfangsversion" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Existierenden Zweig aktualisieren:" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "Nein" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Nur Schnellzusammenführung" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "Zurücksetzen" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Arbeitskopie umstellen nach Erstellen" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Bitte wählen Sie einen Ãœbernahmezweig." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "Ãœbernahmezweig »%s« ist kein Zweig im anderen Projektarchiv." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Bitte geben Sie einen Zweignamen an." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "»%s« ist kein zulässiger Zweigname." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Zweig löschen" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Lokalen Zweig löschen" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Lokale Zweige" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Nur löschen, wenn darin zusammengeführt" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Immer (ohne Zusammenführungstest)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"Gelöschte Zweige können nur mit größerem Aufwand wiederhergestellt werden.\n" +"\n" +"Gewählte Zweige jetzt löschen?" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Fehler beim Löschen der Zweige:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Zweig umbenennen" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Umbenennen" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Zweig:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Neuer Name:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Bitte wählen Sie einen Zweig zum umbenennen." + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "Zweig »%s« existiert bereits." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Fehler beim Umbenennen von »%s«." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "Starten..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "Datei-Browser" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "%s laden..." + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "[Nach oben]" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "Dateien des Zweigs durchblättern" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:391 +#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492 +#: lib/choose_repository.tcl:989 +msgid "Browse" +msgstr "Blättern" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Änderungen »%s« von »%s« anfordern" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "Schließen" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Zweig »%s« existiert nicht." + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"Zweig »%s« existiert bereits.\n" +"\n" +"Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres " +"Zusammenführen ist notwendig." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "Zusammenführungsmethode »%s« nicht unterstützt." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Aktualisieren von »%s« fehlgeschlagen." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "Bereitstellung (»index«) ist zur Bearbeitung gesperrt (»locked«)." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n" +"\n" +"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden " +"geändert. Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n" +"\n" +"Es wird gleich neu geladen.\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Arbeitskopie umstellen auf »%s«..." + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" +"Zweig umstellen von »%s« abgebrochen (Zusammenführen der Dateien ist " +"notwendig)." + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "Zusammenführen der Dateien ist notwendig." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Es wird auf Zweig »%s« verblieben." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Die Arbeitskopie ist nicht auf einem lokalen Zweig.\n" +"\n" +"Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen " +"Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«." + +#: lib/checkout_op.tcl:446 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Umgestellt auf »%s«." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "" +"Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt " +"werden." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "»%s« zurücksetzen?" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "Darstellen" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Lokaler Zweig kann nicht gesetzt werden.\n" +"\n" +"Diese Arbeitskopie ist nur teilweise umgestellt. Die Dateien sind korrekt " +"aktualisiert, aber einige interne Git-Dateien konnten nicht geändert " +"werden.\n" +"\n" +"Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Auswählen" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Schriftfamilie" + +#: lib/choose_font.tcl:73 +msgid "Font Size" +msgstr "Schriftgröße" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "Schriftbeispiel" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Dies ist ein Beispieltext.\n" +"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen." + +#: lib/choose_repository.tcl:27 +msgid "Git Gui" +msgstr "Git Gui" + +#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380 +msgid "Create New Repository" +msgstr "Neues Projektarchiv" + +#: lib/choose_repository.tcl:86 +msgid "New..." +msgstr "Neu..." + +#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468 +msgid "Clone Existing Repository" +msgstr "Projektarchiv kopieren" + +#: lib/choose_repository.tcl:99 +msgid "Clone..." +msgstr "Kopieren..." + +#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978 +msgid "Open Existing Repository" +msgstr "Projektarchiv öffnen" + +#: lib/choose_repository.tcl:112 +msgid "Open..." +msgstr "Öffnen..." + +#: lib/choose_repository.tcl:125 +msgid "Recent Repositories" +msgstr "Zuletzt benutzte Projektarchive" + +#: lib/choose_repository.tcl:131 +msgid "Open Recent Repository:" +msgstr "Zuletzt benutztes Projektarchiv öffnen:" + +#: lib/choose_repository.tcl:294 +#, tcl-format +msgid "Location %s already exists." +msgstr "Projektarchiv »%s« existiert bereits." + +#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307 +#: lib/choose_repository.tcl:314 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Projektarchiv »%s« konnte nicht erstellt werden:" + +#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486 +msgid "Directory:" +msgstr "Verzeichnis:" + +#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544 +#: lib/choose_repository.tcl:1013 +msgid "Git Repository" +msgstr "Git Projektarchiv" + +#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437 +#, tcl-format +msgid "Directory %s already exists." +msgstr "Verzeichnis »%s« existiert bereits." + +#: lib/choose_repository.tcl:442 +#, tcl-format +msgid "File %s already exists." +msgstr "Datei »%s« existiert bereits." + +#: lib/choose_repository.tcl:463 +msgid "Clone" +msgstr "Kopieren" + +#: lib/choose_repository.tcl:476 +msgid "URL:" +msgstr "URL:" + +#: lib/choose_repository.tcl:496 +msgid "Clone Type:" +msgstr "Art der Kopie:" + +#: lib/choose_repository.tcl:502 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Standard (schnell, teilweise redundant, Hardlinks)" + +#: lib/choose_repository.tcl:508 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "Alles kopieren (langsamer, volle Redundanz)" + +#: lib/choose_repository.tcl:514 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)" + +#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 +#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808 +#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Kein Git-Projektarchiv in »%s« gefunden." + +#: lib/choose_repository.tcl:586 +msgid "Standard only available for local repository." +msgstr "Standard ist nur für lokale Projektarchive verfügbar." + +#: lib/choose_repository.tcl:590 +msgid "Shared only available for local repository." +msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar." + +#: lib/choose_repository.tcl:617 +msgid "Failed to configure origin" +msgstr "" + +#: lib/choose_repository.tcl:629 +msgid "Counting objects" +msgstr "" + +#: lib/choose_repository.tcl:630 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:654 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "" + +#: lib/choose_repository.tcl:690 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Von »%s« konnte nichts kopiert werden." + +#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906 +#: lib/choose_repository.tcl:918 +msgid "The 'master' branch has not been initialized." +msgstr "" + +#: lib/choose_repository.tcl:705 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "" + +#: lib/choose_repository.tcl:717 +#, tcl-format +msgid "Cloning from %s" +msgstr "Kopieren von »%s«" + +#: lib/choose_repository.tcl:748 +msgid "Copying objects" +msgstr "Objektdatenbank kopieren" + +#: lib/choose_repository.tcl:749 +msgid "KiB" +msgstr "KB" + +#: lib/choose_repository.tcl:773 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Objekt kann nicht kopiert werden: %s" + +#: lib/choose_repository.tcl:783 +msgid "Linking objects" +msgstr "Objekte verlinken" + +#: lib/choose_repository.tcl:784 +msgid "objects" +msgstr "Objekte" + +#: lib/choose_repository.tcl:792 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Objekt kann nicht hartverlinkt werden: %s" + +#: lib/choose_repository.tcl:847 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:858 +msgid "Cannot fetch tags. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:882 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:891 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "" + +#: lib/choose_repository.tcl:897 +msgid "Clone failed." +msgstr "Kopieren fehlgeschlagen." + +#: lib/choose_repository.tcl:904 +msgid "No default branch obtained." +msgstr "" + +#: lib/choose_repository.tcl:915 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "" + +#: lib/choose_repository.tcl:927 +msgid "Creating working directory" +msgstr "Arbeitskopie erstellen" + +#: lib/choose_repository.tcl:928 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "Dateien" + +#: lib/choose_repository.tcl:957 +msgid "Initial file checkout failed." +msgstr "" + +#: lib/choose_repository.tcl:973 +msgid "Open" +msgstr "Öffnen" + +#: lib/choose_repository.tcl:983 +msgid "Repository:" +msgstr "Projektarchiv:" + +#: lib/choose_repository.tcl:1033 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Projektarchiv »%s« konnte nicht geöffnet werden." + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "Abgetrennte Arbeitskopie-Version" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Version Regexp-Ausdruck:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Lokaler Zweig" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Ãœbernahmezweig" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "Markierung" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Ungültige Version: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Keine Version ausgewählt." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "Versions-Ausdruck ist leer." + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "Aktualisiert" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "URL" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Keine Version zur Nachbesserung vorhanden.\n" +"\n" +"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende " +"Version, die Sie nachbessern könnten.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Nachbesserung währen Zusammenführung nicht möglich.\n" +"\n" +"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht " +"beendet. Sie können keine vorige Ãœbertragung nachbessern, solange eine " +"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung " +"beenden oder abbrechen.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Benutzername konnte nicht bestimmt werden:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n" +"\n" +"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden " +"geändert. Vor dem Eintragen einer neuen Version muss neu geladen werden.\n" +"\n" +"Es wird gleich neu geladen.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n" +"\n" +"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie " +"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Unbekannter Dateizustand »%s«.\n" +"\n" +"Datei »%s« kann nicht eingetragen werden.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Keine Änderungen vorhanden, die eingetragen werden könnten.\n" +"\n" +"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Bitte geben Sie eine Versionsbeschreibung ein.\n" +"\n" +"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n" +"\n" +"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n" +"\n" +"- Zweite Zeile: Leerzeile\n" +"\n" +"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "write-tree fehlgeschlagen:" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "Version »%s« scheint beschädigt zu sein" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Keine Änderungen einzutragen.\n" +"\n" +"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts " +"zusammengeführt.\n" +"\n" +"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "Keine Änderungen, die eingetragen werden können." + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "commit-tree fehlgeschlagen:" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "update-ref fehlgeschlagen:" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Version %s übertragen: %s" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "Verarbeitung. Bitte warten..." + +#: lib/console.tcl:183 +msgid "Success" +msgstr "Erfolgreich" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "Fehler: Kommando fehlgeschlagen" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Anzahl unverknüpfter Objekte" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Festplattenplatz von unverknüpften Objekten" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Anzahl komprimierter Objekte" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Anzahl Komprimierungseinheiten" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Festplattenplatz von komprimierten Objekten" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "Komprimierte Objekte, die zum Entfernen vorgesehen sind" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "Dateien im Mülleimer" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Objektdatenbank komprimieren" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n" +"\n" +"Für eine optimale Performance wird empfohlen, die Datenbank des " +"Projektarchivs zu komprimieren, sobald mehr als %i nicht verknüpfte Objekte " +"vorliegen.\n" +"\n" +"Soll die Datenbank jetzt komprimiert werden?" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Ungültiges Datum von Git: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Keine Änderungen feststellbar.\n" +"\n" +"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei " +"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist " +"unverändert.\n" +"\n" +"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen " +"Dateien zu prüfen." + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Vergleich von »%s« laden..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "Datei »%s« kann nicht angezeigt werden" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Fehler beim Laden der Datei:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Git-Projektarchiv (Unterprojekt)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* Binärdatei (Inhalt wird nicht angezeigt)" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Fehler beim Laden des Vergleichs:" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "Fehler beim Herausnehmen der gewählten Dateien aus der Bereitstellung." + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "Fehler beim Bereitstellen der gewählten Dateien." + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "Fehler" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "Warnung" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "" +"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können." + +#: lib/index.tcl:241 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "Datei »%s« aus der Bereitstellung herausnehmen" + +#: lib/index.tcl:285 +#, tcl-format +msgid "Adding %s" +msgstr "»%s« hinzufügen..." + +#: lib/index.tcl:340 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Änderungen in Datei »%s« revidieren?" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Änderungen in den gewählten %i Dateien revidieren?" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Alle nicht bereitgestellten Änderungen werden beim Revidieren verloren gehen." + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "Nichts tun" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n" +"\n" +"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie " +"zusammenführen können.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n" +"\n" +"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden " +"geändert. Vor einem Zusammenführen muss neu geladen werden.\n" +"\n" +"Es wird gleich neu geladen.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Zusammenführung mit Konflikten.\n" +"\n" +"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese " +"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder " +"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst " +"danach kann eine neue Zusammenführung begonnen werden.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Es liegen Änderungen vor.\n" +"\n" +"Die Datei »%s« wurde geändert. Sie sollten zuerst die bereitgestellte " +"Version abschließen, bevor Sie eine Zusammenführung beginnen. Mit dieser " +"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich " +"einfacher beheben oder abbrechen.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s von %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "Zusammenführen von %s und %s" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "Zusammenführen erfolgreich abgeschlossen." + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig." + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "Zusammenführen in %s" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "Zusammenzuführende Version" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Abbruch der Nachbesserung ist nicht möglich.\n" +"\n" +"Sie müssen die Nachbesserung der Version abschließen.\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Zusammenführen abbrechen?\n" +"\n" +"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen " +"verloren.\n" +"\n" +"Zusammenführen jetzt abbrechen?" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Änderungen zurücksetzen?\n" +"\n" +"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen " +"verloren.\n" +"\n" +"Änderungen jetzt zurücksetzen?" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "Abbruch" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "Abbruch fehlgeschlagen." + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "Abbruch durchgeführt. Bereit." + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "Voreinstellungen wiederherstellen" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "Speichern" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "Projektarchiv %s" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "Global (Alle Projektarchive)" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "Benutzername" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "E-Mail-Adresse" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "Zusammenführungs-Versionen zusammenfassen" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "Ausführlichkeit der Zusammenführen-Meldungen" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "Vergleichsstatistik nach Zusammenführen anzeigen" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "Auf Dateiänderungsdatum verlassen" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "Ãœbernahmezweige entfernen während Anforderung" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "Passend zu Ãœbernahmezweig" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "Anzahl der Kontextzeilen beim Vergleich" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "Namensvorschlag für neue Zweige" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "Schriftart ändern" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "%s wählen" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "pt." + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "Einstellungen" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "Optionen konnten nicht gespeichert werden:" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Zweig im anderen Projektarchiv löschen" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Von Projektarchiv" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "Anderes Archiv:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "Kommunikation mit URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Zweige" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Löschen, falls" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Zusammenführen mit:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Immer (Keine Zusammenführungsprüfung)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"Folgende Zweige sind noch nicht mit »%s« zusammengeführt:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die " +"notwendigen Versionen vorher angefordert haben. Sie sollten versuchen, " +"zuerst von »%s« anzufordern." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand " +"möglich.\n" +"\n" +"Sollen die ausgewählten Zweige gelöscht werden?" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Zweige auf »%s« werden gelöscht" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Kein Projektarchiv ausgewählt." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "»%s« laden..." + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "Entfernen von" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Anfordern von" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Versenden nach" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "Fehler beim Schreiben der Verknüpfung:" + +#: lib/shortcut.tcl:136 +msgid "Cannot write icon:" +msgstr "Fehler beim Erstellen des Icons:" + +#: lib/status_bar.tcl:83 +#, tcl-format +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" +msgstr "Neue Änderungen von »%s« holen" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "Entfernen von »%s« im anderen Archiv" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "Ãœbernahmezweige 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" +msgstr "Änderungen nach »%s« versenden" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "%s %s nach %s versenden" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Zweige versenden" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "Herkunftszweige" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Ziel-Projektarchiv" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "Netzwerk-Einstellungen" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" +"Ãœberschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Mit Markierungen übertragen" + +#~ msgid "Next >" +#~ msgstr "Weiter >" + +#~ msgid "Fetch" +#~ msgstr "Anfordern" + +#~ msgid "Unstaged Changes (Will Not Be Committed)" +#~ msgstr "Nicht bereitgestellte Änderungen (werden nicht eingetragen)" diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot new file mode 100644 index 0000000000..00f0f5922a --- /dev/null +++ b/git-gui/po/git-gui.pot @@ -0,0 +1,1704 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "" + +#: git-gui.sh:595 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "" + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" + +#: git-gui.sh:853 +msgid "Git directory not found:" +msgstr "" + +#: git-gui.sh:860 +msgid "Cannot move to top of working directory:" +msgstr "" + +#: git-gui.sh:867 +msgid "Cannot use funny .git directory:" +msgstr "" + +#: git-gui.sh:872 +msgid "No working directory" +msgstr "" + +#: git-gui.sh:1019 +msgid "Refreshing file status..." +msgstr "" + +#: git-gui.sh:1084 +msgid "Scanning for modified files ..." +msgstr "" + +#: git-gui.sh:1259 lib/browser.tcl:245 +msgid "Ready." +msgstr "" + +#: git-gui.sh:1525 +msgid "Unmodified" +msgstr "" + +#: git-gui.sh:1527 +msgid "Modified, not staged" +msgstr "" + +#: git-gui.sh:1528 git-gui.sh:1533 +msgid "Staged for commit" +msgstr "" + +#: git-gui.sh:1529 git-gui.sh:1534 +msgid "Portions staged for commit" +msgstr "" + +#: git-gui.sh:1530 git-gui.sh:1535 +msgid "Staged for commit, missing" +msgstr "" + +#: git-gui.sh:1532 +msgid "Untracked, not staged" +msgstr "" + +#: git-gui.sh:1537 +msgid "Missing" +msgstr "" + +#: git-gui.sh:1538 +msgid "Staged for removal" +msgstr "" + +#: git-gui.sh:1539 +msgid "Staged for removal, still present" +msgstr "" + +#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +msgid "Requires merge resolution" +msgstr "" + +#: git-gui.sh:1579 +msgid "Starting gitk... please wait..." +msgstr "" + +#: git-gui.sh:1588 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" + +#: git-gui.sh:1788 lib/choose_repository.tcl:32 +msgid "Repository" +msgstr "" + +#: git-gui.sh:1789 +msgid "Edit" +msgstr "" + +#: git-gui.sh:1791 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "" + +#: git-gui.sh:1794 lib/choose_rev.tcl:547 +msgid "Commit@@noun" +msgstr "" + +#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "" + +#: git-gui.sh:1798 lib/choose_rev.tcl:556 +msgid "Remote" +msgstr "" + +#: git-gui.sh:1807 +msgid "Browse Current Branch's Files" +msgstr "" + +#: git-gui.sh:1811 +msgid "Browse Branch Files..." +msgstr "" + +#: git-gui.sh:1816 +msgid "Visualize Current Branch's History" +msgstr "" + +#: git-gui.sh:1820 +msgid "Visualize All Branch History" +msgstr "" + +#: git-gui.sh:1827 +#, tcl-format +msgid "Browse %s's Files" +msgstr "" + +#: git-gui.sh:1829 +#, tcl-format +msgid "Visualize %s's History" +msgstr "" + +#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "" + +#: git-gui.sh:1837 lib/database.tcl:34 +msgid "Compress Database" +msgstr "" + +#: git-gui.sh:1840 +msgid "Verify Database" +msgstr "" + +#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 +#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +msgid "Create Desktop Icon" +msgstr "" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +msgid "Quit" +msgstr "" + +#: git-gui.sh:1867 +msgid "Undo" +msgstr "" + +#: git-gui.sh:1870 +msgid "Redo" +msgstr "" + +#: git-gui.sh:1874 git-gui.sh:2366 +msgid "Cut" +msgstr "" + +#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "" + +#: git-gui.sh:1880 git-gui.sh:2372 +msgid "Paste" +msgstr "" + +#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "" + +#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +msgid "Select All" +msgstr "" + +#: git-gui.sh:1896 +msgid "Create..." +msgstr "" + +#: git-gui.sh:1902 +msgid "Checkout..." +msgstr "" + +#: git-gui.sh:1908 +msgid "Rename..." +msgstr "" + +#: git-gui.sh:1913 git-gui.sh:2012 +msgid "Delete..." +msgstr "" + +#: git-gui.sh:1918 +msgid "Reset..." +msgstr "" + +#: git-gui.sh:1930 git-gui.sh:2313 +msgid "New Commit" +msgstr "" + +#: git-gui.sh:1938 git-gui.sh:2320 +msgid "Amend Last Commit" +msgstr "" + +#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "" + +#: git-gui.sh:1953 +msgid "Stage To Commit" +msgstr "" + +#: git-gui.sh:1958 +msgid "Stage Changed Files To Commit" +msgstr "" + +#: git-gui.sh:1964 +msgid "Unstage From Commit" +msgstr "" + +#: git-gui.sh:1969 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "" + +#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +msgid "Sign Off" +msgstr "" + +#: git-gui.sh:1980 git-gui.sh:2296 +msgid "Commit@@verb" +msgstr "" + +#: git-gui.sh:1991 +msgid "Local Merge..." +msgstr "" + +#: git-gui.sh:1996 +msgid "Abort Merge..." +msgstr "" + +#: git-gui.sh:2008 +msgid "Push..." +msgstr "" + +#: git-gui.sh:2019 lib/choose_repository.tcl:41 +msgid "Apple" +msgstr "" + +#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "" + +#: git-gui.sh:2026 +msgid "Preferences..." +msgstr "" + +#: git-gui.sh:2034 git-gui.sh:2558 +msgid "Options..." +msgstr "" + +#: git-gui.sh:2040 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "" + +#: git-gui.sh:2081 +msgid "Online Documentation" +msgstr "" + +#: git-gui.sh:2165 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" + +#: git-gui.sh:2198 +msgid "Current Branch:" +msgstr "" + +#: git-gui.sh:2219 +msgid "Staged Changes (Will Commit)" +msgstr "" + +#: git-gui.sh:2239 +msgid "Unstaged Changes" +msgstr "" + +#: git-gui.sh:2286 +msgid "Stage Changed" +msgstr "" + +#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "" + +#: git-gui.sh:2332 +msgid "Initial Commit Message:" +msgstr "" + +#: git-gui.sh:2333 +msgid "Amended Commit Message:" +msgstr "" + +#: git-gui.sh:2334 +msgid "Amended Initial Commit Message:" +msgstr "" + +#: git-gui.sh:2335 +msgid "Amended Merge Commit Message:" +msgstr "" + +#: git-gui.sh:2336 +msgid "Merge Commit Message:" +msgstr "" + +#: git-gui.sh:2337 +msgid "Commit Message:" +msgstr "" + +#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +msgid "Copy All" +msgstr "" + +#: git-gui.sh:2406 lib/blame.tcl:104 +msgid "File:" +msgstr "" + +#: git-gui.sh:2508 +msgid "Refresh" +msgstr "" + +#: git-gui.sh:2529 +msgid "Apply/Reverse Hunk" +msgstr "" + +#: git-gui.sh:2535 +msgid "Decrease Font Size" +msgstr "" + +#: git-gui.sh:2539 +msgid "Increase Font Size" +msgstr "" + +#: git-gui.sh:2544 +msgid "Show Less Context" +msgstr "" + +#: git-gui.sh:2551 +msgid "Show More Context" +msgstr "" + +#: git-gui.sh:2565 +msgid "Unstage Hunk From Commit" +msgstr "" + +#: git-gui.sh:2567 +msgid "Stage Hunk For Commit" +msgstr "" + +#: git-gui.sh:2586 +msgid "Initializing..." +msgstr "" + +#: git-gui.sh:2677 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" + +#: git-gui.sh:2707 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" + +#: git-gui.sh:2712 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "" + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "" + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "" + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "" + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "" + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "" + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "" + +#: lib/blame.tcl:791 +msgid "Committer:" +msgstr "" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 +msgid "Create" +msgstr "" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "" + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "" + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "" + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "" + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "" + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "" + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "" + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "" + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:215 +#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 +#: lib/choose_repository.tcl:811 +msgid "Browse" +msgstr "" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "" + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "" + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "" + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "" + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "" + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "" + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" + +#: lib/checkout_op.tcl:446 +#, tcl-format +msgid "Checked out '%s'." +msgstr "" + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "" + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "" + +#: lib/choose_font.tcl:73 +msgid "Font Size" +msgstr "" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" + +#: lib/choose_repository.tcl:25 +msgid "Git Gui" +msgstr "" + +#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 +msgid "Create New Repository" +msgstr "" + +#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 +msgid "Clone Existing Repository" +msgstr "" + +#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 +msgid "Open Existing Repository" +msgstr "" + +#: lib/choose_repository.tcl:91 +msgid "Next >" +msgstr "" + +#: lib/choose_repository.tcl:152 +#, tcl-format +msgid "Location %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 +#: lib/choose_repository.tcl:172 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "" + +#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +msgid "Directory:" +msgstr "" + +#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 +#: lib/choose_repository.tcl:834 +msgid "Git Repository" +msgstr "" + +#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#, tcl-format +msgid "Directory %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:265 +#, tcl-format +msgid "File %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:286 +msgid "Clone" +msgstr "" + +#: lib/choose_repository.tcl:299 +msgid "URL:" +msgstr "" + +#: lib/choose_repository.tcl:319 +msgid "Clone Type:" +msgstr "" + +#: lib/choose_repository.tcl:325 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "" + +#: lib/choose_repository.tcl:331 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "" + +#: lib/choose_repository.tcl:337 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "" + +#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 +#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 +#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "" + +#: lib/choose_repository.tcl:405 +msgid "Standard only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:409 +msgid "Shared only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:439 +msgid "Failed to configure origin" +msgstr "" + +#: lib/choose_repository.tcl:451 +msgid "Counting objects" +msgstr "" + +#: lib/choose_repository.tcl:452 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:476 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "" + +#: lib/choose_repository.tcl:512 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "" + +#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:740 +msgid "The 'master' branch has not been initialized." +msgstr "" + +#: lib/choose_repository.tcl:527 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "" + +#: lib/choose_repository.tcl:539 +#, tcl-format +msgid "Cloning from %s" +msgstr "" + +#: lib/choose_repository.tcl:570 +msgid "Copying objects" +msgstr "" + +#: lib/choose_repository.tcl:571 +msgid "KiB" +msgstr "" + +#: lib/choose_repository.tcl:595 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "" + +#: lib/choose_repository.tcl:605 +msgid "Linking objects" +msgstr "" + +#: lib/choose_repository.tcl:606 +msgid "objects" +msgstr "" + +#: lib/choose_repository.tcl:614 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "" + +#: lib/choose_repository.tcl:669 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:680 +msgid "Cannot fetch tags. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:704 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:713 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "" + +#: lib/choose_repository.tcl:719 +msgid "Clone failed." +msgstr "" + +#: lib/choose_repository.tcl:726 +msgid "No default branch obtained." +msgstr "" + +#: lib/choose_repository.tcl:737 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "" + +#: lib/choose_repository.tcl:749 +msgid "Creating working directory" +msgstr "" + +#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "" + +#: lib/choose_repository.tcl:779 +msgid "Initial file checkout failed." +msgstr "" + +#: lib/choose_repository.tcl:795 +msgid "Open" +msgstr "" + +#: lib/choose_repository.tcl:805 +msgid "Repository:" +msgstr "" + +#: lib/choose_repository.tcl:854 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "" + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "" + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "" + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "" + +#: lib/console.tcl:183 +msgid "Success" +msgstr "" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "" + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "" + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "" + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "" + +#: lib/index.tcl:241 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "" + +#: lib/index.tcl:285 +#, tcl-format +msgid "Adding %s" +msgstr "" + +#: lib/index.tcl:340 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "" + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "" + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "" + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "" + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "" + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "" + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "" + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "" + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "" + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "" + +#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 +msgid "Cannot write script:" +msgstr "" + +#: lib/shortcut.tcl:149 +msgid "Cannot write icon:" +msgstr "" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "" diff --git a/git-gui/po/glossary/Makefile b/git-gui/po/glossary/Makefile new file mode 100644 index 0000000000..749aa2e7ec --- /dev/null +++ b/git-gui/po/glossary/Makefile @@ -0,0 +1,9 @@ +PO_TEMPLATE = git-gui-glossary.pot + +ALL_POFILES = $(wildcard *.po) + +$(PO_TEMPLATE): $(subst .pot,.txt,$(PO_TEMPLATE)) + ./txt-to-pot.sh $< > $@ + +update-po:: git-gui-glossary.pot + $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; ) diff --git a/git-gui/po/glossary/de.po b/git-gui/po/glossary/de.po new file mode 100644 index 0000000000..c94786c6ab --- /dev/null +++ b/git-gui/po/glossary/de.po @@ -0,0 +1,185 @@ +# Translation of git-gui glossary to German +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git package. +# Christian Stimming <stimming@tuhh.de>, 2007 +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui glossary\n" +"POT-Creation-Date: 2007-10-19 21:43+0200\n" +"PO-Revision-Date: 2007-10-20 15:24+0200\n" +"Last-Translator: Christian Stimming <stimming@tuhh.de>\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "" +"English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" +"Deutsche Ãœbersetzung.\n" +"Andere deutsche SCM:\n" +" http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://" +"tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po " +"(username=guest, password empty, gut),\n" +" http://msdn.microsoft.com/de-de/library/ms181038(vs.80).aspx (MS Visual " +"Source Safe, kommerziell),\n" +" http://cvsbook.red-bean.com/translations/german/Kap_06.html " +"(mittelmäßig),\n" +" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/de_DE.po?" +"view=markup (mittelmäßig),\n" +" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po " +"(username=guest, password empty, schlecht)" + +#. "" +msgid "amend" +msgstr "nachbessern (ergänzen)" + +#. "" +msgid "annotate" +msgstr "annotieren" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "Zweig" + +#. "" +msgid "branch [verb]" +msgstr "verzweigen" + +#. "" +msgid "checkout [noun]" +msgstr "" +"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? " +"Source Safe: Auscheckvorgang)" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "" +"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? " +"ausspielen? abrufen? Source Safe: auschecken)" + +#. "" +msgid "clone [verb]" +msgstr "kopieren" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "" +"Version; Eintragung; Änderung (Buchung?, Eintragung?, Ãœbertragung?, " +"Sendung?, Ãœbergabe?, Einspielung?, Ablagevorgang?)" + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "" +"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, " +"übergeben?, einspielen?, einpflegen?, ablegen?)" + +#. "" +msgid "diff [noun]" +msgstr "Vergleich (Source Safe: Unterschiede)" + +#. "" +msgid "diff [verb]" +msgstr "vergleichen" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "Schnellzusammenführung" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "anfordern (holen?)" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "Bereitstellung" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "Zusammenführung" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "zusammenführen" + +#. "" +msgid "message" +msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)" + +#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +msgid "prune" +msgstr "entfernen" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "übernehmen (ziehen?)" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)" + +#. "" +msgid "redo" +msgstr "wiederholen" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote" +msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "Projektarchiv" + +#. "" +msgid "reset" +msgstr "zurücksetzen (zurückkehren?)" + +#. "" +msgid "revert" +msgstr "revidieren" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)" + +#. "" +msgid "sign off" +msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)" + +#. "" +msgid "staging area" +msgstr "Bereitstellung" + +#. "" +msgid "status" +msgstr "Status" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "Markierung" + +#. "" +msgid "tag [verb]" +msgstr "markieren" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "Ãœbernahmezweig" + +#. "" +msgid "undo" +msgstr "rückgängig" + +#. "" +msgid "update" +msgstr "aktualisieren" + +#. "" +msgid "verify" +msgstr "überprüfen" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "Arbeitskopie" diff --git a/git-gui/po/glossary/git-gui-glossary.pot b/git-gui/po/glossary/git-gui-glossary.pot new file mode 100644 index 0000000000..48af803314 --- /dev/null +++ b/git-gui/po/glossary/git-gui-glossary.pot @@ -0,0 +1,164 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Free Software Foundation, Inc. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2007-10-19 21:43+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" + +#. "" +msgid "amend" +msgstr "" + +#. "" +msgid "annotate" +msgstr "" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "" + +#. "" +msgid "branch [verb]" +msgstr "" + +#. "" +msgid "checkout [noun]" +msgstr "" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "" + +#. "" +msgid "clone [verb]" +msgstr "" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "" + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "" + +#. "" +msgid "diff [noun]" +msgstr "" + +#. "" +msgid "diff [verb]" +msgstr "" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "" + +#. "" +msgid "message" +msgstr "" + +#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +msgid "prune" +msgstr "" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "" + +#. "" +msgid "redo" +msgstr "" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote" +msgstr "" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "" + +#. "" +msgid "reset" +msgstr "" + +#. "" +msgid "revert" +msgstr "" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "" + +#. "" +msgid "sign off" +msgstr "" + +#. "" +msgid "staging area" +msgstr "" + +#. "" +msgid "status" +msgstr "" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "" + +#. "" +msgid "tag [verb]" +msgstr "" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "" + +#. "" +msgid "undo" +msgstr "" + +#. "" +msgid "update" +msgstr "" + +#. "" +msgid "verify" +msgstr "" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "" + diff --git a/git-gui/po/glossary/git-gui-glossary.txt b/git-gui/po/glossary/git-gui-glossary.txt new file mode 100644 index 0000000000..500d0a0ea7 --- /dev/null +++ b/git-gui/po/glossary/git-gui-glossary.txt @@ -0,0 +1,37 @@ +"English Term (Dear translator: This file will never be visible to the user!)" "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +"amend" "" +"annotate" "" +"branch [noun]" "A 'branch' is an active line of development." +"branch [verb]" "" +"checkout [noun]" "" +"checkout [verb]" "The action of updating the working tree to a revision which was stored in the object database." +"clone [verb]" "" +"commit [noun]" "A single point in the git history." +"commit [verb]" "The action of storing a new snapshot of the project's state in the git history." +"diff [noun]" "" +"diff [verb]" "" +"fast forward merge" "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +"fetch" "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +"index (in git-gui: staging area)" "A collection of files. The index is a stored version of your working tree." +"merge [noun]" "A successful merge results in the creation of a new commit representing the result of the merge." +"merge [verb]" "To bring the contents of another branch into the current branch." +"message" "" +"prune" "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +"pull" "Pulling a branch means to fetch it and merge it." +"push" "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +"redo" "" +"remote" "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +"repository" "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +"reset" "" +"revert" "" +"revision" "A particular state of files and directories which was stored in the object database." +"sign off" "" +"staging area" "" +"status" "" +"tag [noun]" "A ref pointing to a tag or commit object" +"tag [verb]" "" +"tracking branch" "A regular git branch that is used to follow changes from another repository." +"undo" "" +"update" "" +"verify" "" +"working copy, working tree" "The tree of actual checked out files." diff --git a/git-gui/po/glossary/it.po b/git-gui/po/glossary/it.po new file mode 100644 index 0000000000..8e3d9a2f07 --- /dev/null +++ b/git-gui/po/glossary/it.po @@ -0,0 +1,180 @@ +# Translation of git-gui glossary to Italian +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git package. +# Christian Stimming <stimming@tuhh.de>, 2007 +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui glossary\n" +"POT-Creation-Date: 2007-10-05 22:30+0200\n" +"PO-Revision-Date: 2007-10-10 15:24+0200\n" +"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n" +"Language-Team: Italian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "" +"English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" +"Traduzione italiana.\n" +"Altri SCM in italiano:\n" +" http://tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_it." +"po (username=guest, password empty),\n" +" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/it_IT.po?" +"view=markup ,\n" +" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/it_IT/rapidsvn.po " +"(username=guest, password empty)" + +#. "" +msgid "amend" +msgstr "correggere, correzione" + +#. "" +msgid "annotate" +msgstr "annotare, annotazione" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "ramo, diramazione, ramificazione" + +#. "" +msgid "branch [verb]" +msgstr "creare ramo, ramificare, diramare" + +#. "" +msgid "checkout [noun]" +msgstr "attivazione, checkout, revisione attiva, prelievo (TortoiseCVS)?" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "" +"attivare, effettuare un checkout, attivare revisione, prelevare (TortoiseCVS), " +"ritirare (TSVN)?" + +#. "" +msgid "clone [verb]" +msgstr "clonare" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "revisione, commit, deposito (TortoiseCVS), invio (TSVN)?" + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "" +"creare una nuova revisione, archiviare, effettuare un commit, depositare " +"(nel server), fare un deposito (TortoiseCVS), inviare (TSVN)?" + +#. "" +msgid "diff [noun]" +msgstr "differenza, confronto, comparazione, raffronto" + +#. "" +msgid "diff [verb]" +msgstr "confronta, mostra le differenze" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "fusione in 'fast-forward', fusione in avanti veloce" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "recuperare, prelevare, prendere da, recuperare (TSVN)" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "indice" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "fusione, unione" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "effettuare la fusione, unire, fondere, eseguire la fusione" + +#. "" +msgid "message" +msgstr "messaggio, commento" + +#. "" +msgid "prune" +msgstr "potatura" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "" +"prendi (recupera) e fondi (unisci)? (in pratica una traduzione di fetch + " +"merge)" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "propaga" + +#. "" +msgid "redo" +msgstr "ripeti, rifai" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "archivio, repository, database? deposito (rapidsvn)?" + +#. "" +msgid "reset" +msgstr "ripristinare, annullare, azzerare, ripristinare" + +#. "" +msgid "revert" +msgstr "" +"annullare, inverti (rapidsvn), ritorna allo stato precedente, annulla le " +"modifiche della revisione" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "revisione (TortoiseSVN)" + +#. "" +msgid "sign off" +msgstr "sign off, firma" + +#. "" +msgid "staging area" +msgstr "" +"area di preparazione, zona di preparazione, modifiche in preparazione? " +"modifiche in allestimento?" + +#. "" +msgid "status" +msgstr "stato" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "etichetta, etichettatura (TortoiseCVS)" + +#. "" +msgid "tag [verb]" +msgstr "etichettare" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "" +"duplicato locale di ramo remoto, ramo in 'tracking', ramo inseguitore? ramo di {inseguimento,allineamento," +"rilevamento,puntamento}?" + +#. "" +msgid "undo" +msgstr "annulla" + +#. "" +msgid "update" +msgstr "aggiornamento, aggiornare" + +#. "" +msgid "verify" +msgstr "verifica, verificare" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "directory di lavoro, copia di lavoro" diff --git a/git-gui/po/glossary/txt-to-pot.sh b/git-gui/po/glossary/txt-to-pot.sh new file mode 100755 index 0000000000..49bf7c5365 --- /dev/null +++ b/git-gui/po/glossary/txt-to-pot.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# This is a very, _very_, simple script to convert a tab-separated +# .txt file into a .pot/.po. +# Its not clever but it took me 2 minutes to write :) +# Michael Twomey <michael.twomey@ireland.sun.com> +# 23 March 2001 +# with slight GnuCash modifications by Christian Stimming <stimming@tuhh.de> +# 19 Aug 2001, 23 Jul 2007 + +#check args +if [ $# -eq 0 ] +then + cat <<! +Usage: `basename $0` git-gui-glossary.txt > git-gui-glossary.pot +! + exit 1; +fi + +GLOSSARY_CSV="$1"; + +if [ ! -f "$GLOSSARY_CSV" ] +then + echo "Can't find $GLOSSARY_CSV."; + exit 1; +fi + +cat <<! +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Free Software Foundation, Inc. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: `date +'%Y-%m-%d %H:%M%z'`\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" + +! + +#Yes this is the most simple awk script you've ever seen :) +awk -F'\t' '{if ($2 != "") print "#. "$2; print "msgid "$1; print "msgstr \"\"\n"}' \ +$GLOSSARY_CSV diff --git a/git-gui/po/glossary/zh_cn.po b/git-gui/po/glossary/zh_cn.po new file mode 100644 index 0000000000..158835b5c1 --- /dev/null +++ b/git-gui/po/glossary/zh_cn.po @@ -0,0 +1,170 @@ +# Translation of git-gui glossary to Simplified Chinese +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git package. +# Xudong Guan <xudong.guan@gmail.com> and the zh-kernel.org mailing list, 2007 +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui glossary\n" +"PO-Revision-Date: 2007-07-23 22:07+0200\n" +"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n" +"Language-Team: Simplified Chinese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "" +"English Term (Dear translator: This file will never be visible to the user!)" +msgstr "注:这个文件是为了帮助翻译人员统一åè¯æœ¯è¯ã€‚最终用户ä¸ä¼šå…³å¿ƒè¿™ä¸ªæ–‡ä»¶ã€‚" + +#. "" +#. amend指用户修改最近一次commitçš„æ“作,修订?修改?修æ£ï¼Ÿ +#. [WANG Cong]: æ ¹æ®æˆ‘的了解,这个è¯ä¼¼ä¹Žç¿»è¯‘æˆâ€œä¿®è®¢â€å¤šä¸€äº›ã€‚“修æ£â€ä¹Ÿå¯ä»¥ï¼Œâ€œä¿®æ”¹â€å†æ¬¡ä¹‹ã€‚ +#. [ZHANG Le]: 修订,感觉一般指对一些大型出版物的大规模å‡çº§ï¼Œæ¯”如修订新åŽå—å…¸ +# ä¿®æ£ï¼Œå…¶å®žæ¯æ¬¡amend的结果也ä¸ä¸€å®šå°±æ˜¯æœ€åŽç»“果,说ä¸å®šè¿˜éœ€è¦ä¿®æ”¹ã€‚æ‰€ä»¥ä¸ +# 如就å«ä¿®æ”¹ +msgid "amend" +msgstr "修订" + +#. "" +#. git annotate 文件å:用æ¥æ ‡æ³¨æ–‡ä»¶çš„æ¯ä¸€è¡Œåœ¨ä»€ä¹ˆæ—¶å€™è¢«è°æœ€åŽä¿®æ”¹ã€‚ +#. [WANG Cong]: "æ ‡è®°"一般是mark。;) +#. [ZHANG Le]: æ ‡æ³¨ï¼Œæˆ–è€…å¹²è„†ç”¨åŽŸæ„:注解,或注释 +msgid "annotate" +msgstr "æ ‡æ³¨" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "分支" + +#. "" +msgid "branch [verb]" +msgstr "建立分支" + +#. "" +#. [WANG Cong]: 网上有人翻译æˆâ€œæ£€å‡ºâ€ï¼Œæˆ‘感觉更好一些,毕竟把checkçš„æ„æ€ç¿»è¯‘出æ¥äº†ã€‚ +#. [ZHNAG Le]: æå–å§ï¼Œæå–分支ï¼ç‰ˆæœ¬ +#. [rae l]: ç¾å‡ºã€‚subversion软件ä¸çš„大多è¯æ±‡å·²æœ‰ç¿»è¯‘,既然git与subversionåŒæ˜¯SCM管ç†ï¼Œå¯ä»¥å‚考åŒç±»è½¯ä»¶çš„翻译也ä¸é”™ã€‚ +msgid "checkout [noun]" +msgstr "ç¾å‡º" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "ç¾å‡º" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "æ交" + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "æ交" + +#. "" +#. 差异?差别? +#. [ZHANG Le]: ä¸ªäººæ„Ÿè§‰å·®åˆ«æ›´åŠ ä¸æ€§ä¸€äº› +msgid "diff [noun]" +msgstr "差别" + +#. "" +msgid "diff [verb]" +msgstr "比较" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "å¿«è¿›å¼åˆå¹¶" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +#. 获å–?å–得?下载?更新?注æ„å’Œupdate的区分 +msgid "fetch" +msgstr "获å–" + +#. "A collection of files. The index is a stored version of your working tree." +#. index是working treeå’Œrepositoryä¹‹é—´çš„ç¼“å˜ +msgid "index (in git-gui: staging area)" +msgstr "工作缓å˜ï¼Ÿ" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "åˆå¹¶" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "åˆå¹¶" + +#. "" +#. message是指commitä¸çš„æ–‡å—ä¿¡æ¯ +msgid "message" +msgstr "æè¿°" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "获å–+åˆå¹¶" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "推入" + +#. "" +msgid "redo" +msgstr "é‡åš" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "仓库" + +#. "" +msgid "reset" +msgstr "é‡ç½®" + +#. "" +msgid "revert" +msgstr "æ¢å¤" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "版本" + +#. "" +msgid "sign off" +msgstr "ç¾å" + +#. "" +#. 似乎是git-gui里é¢æ˜¾ç¤ºçš„本次æ交的文件清å•åŒºåŸŸ +msgid "staging area" +msgstr "æ交暂å˜åŒº" + +#. "" +msgid "status" +msgstr "状æ€" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "æ ‡ç¾" + +#. "" +msgid "tag [verb]" +msgstr "æ·»åŠ æ ‡ç¾" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "跟踪分支" + +#. "" +msgid "undo" +msgstr "撤销" + +#. "" +msgid "update" +msgstr "更新。注æ„å’Œfetch的区分" + +#. "" +msgid "verify" +msgstr "验è¯" + +#. "The tree of actual checked out files." +#. "工作副本?工作区域?工作目录" +#. [LI Yang]: 当å‰å‰¯æœ¬ï¼Œ 当å‰æºç æ ‘ï¼Ÿ +msgid "working copy, working tree" +msgstr "工作副本,工作æºç æ ‘" diff --git a/git-gui/po/hu.po b/git-gui/po/hu.po new file mode 100644 index 0000000000..e8c04f7aea --- /dev/null +++ b/git-gui/po/hu.po @@ -0,0 +1,1895 @@ +# Hungarian translations for git-gui-i package. +# Copyright (C) 2007 THE git-gui-i'S COPYRIGHT HOLDER +# This file is distributed under the same license as the git-gui-i package. +# Miklos Vajna <vmiklos@frugalware.org>, 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui-i 18n\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"PO-Revision-Date: 2007-07-27 13:15+0200\n" +"Last-Translator: Miklos Vajna <vmiklos@frugalware.org>\n" +"Language-Team: Hungarian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "" + +#: git-gui.sh:595 +#, fuzzy, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Érvénytelen font lett megadva a grafikus felületben.%s:" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "FÅ‘ betűtÃpus" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "Diff/konzol betűtÃpus" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "A git nem található a PATH-ban." + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "Nem értelmezhetÅ‘ a Git verzió sztring:" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Nem állÃpÃtható meg a Git verziója.\n" +"\n" +"A(z) %s szerint a verzió '%s'.\n" +"\n" +"A(z) %s a Git 1.5.0 vagy késÅ‘bbi verzióját igényli.\n" +"\n" +"Feltételezhetjük, hogy a(z) '%s' verziója legalább 1.5.0?\n" + +#: git-gui.sh:853 +msgid "Git directory not found:" +msgstr "A Git könyvtár nem található:" + +#: git-gui.sh:860 +#, fuzzy +msgid "Cannot move to top of working directory:" +msgstr "Nem használható vicces .git könyvtár:" + +#: git-gui.sh:867 +msgid "Cannot use funny .git directory:" +msgstr "Nem használható vicces .git könyvtár:" + +#: git-gui.sh:872 +msgid "No working directory" +msgstr "Nincs munkakönyvtár" + +#: git-gui.sh:1019 +msgid "Refreshing file status..." +msgstr "A fájlok státuszának frissÃtése..." + +#: git-gui.sh:1084 +msgid "Scanning for modified files ..." +msgstr "MódosÃtott fájlok keresése ..." + +#: git-gui.sh:1259 lib/browser.tcl:245 +msgid "Ready." +msgstr "Kész." + +#: git-gui.sh:1525 +msgid "Unmodified" +msgstr "Nem módosÃtott" + +#: git-gui.sh:1527 +msgid "Modified, not staged" +msgstr "MódosÃtott, de nem kiválasztott" + +#: git-gui.sh:1528 git-gui.sh:1533 +msgid "Staged for commit" +msgstr "Kiválasztva commitolásra" + +#: git-gui.sh:1529 git-gui.sh:1534 +msgid "Portions staged for commit" +msgstr "Részek kiválasztva commitolásra" + +#: git-gui.sh:1530 git-gui.sh:1535 +msgid "Staged for commit, missing" +msgstr "Kiválasztva commitolásra, hiányzó" + +#: git-gui.sh:1532 +msgid "Untracked, not staged" +msgstr "Nem követett, nem kiválasztott" + +#: git-gui.sh:1537 +msgid "Missing" +msgstr "Hiányzó" + +#: git-gui.sh:1538 +msgid "Staged for removal" +msgstr "Kiválasztva eltávolÃtásra" + +#: git-gui.sh:1539 +msgid "Staged for removal, still present" +msgstr "Kiválasztva eltávolÃtásra, jelenleg is elérhetÅ‘" + +#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +msgid "Requires merge resolution" +msgstr "Merge feloldás szükséges" + +#: git-gui.sh:1579 +msgid "Starting gitk... please wait..." +msgstr "A gitk indÃtása... várjunk..." + +#: git-gui.sh:1588 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"A gitk indÃtása sikertelen:\n" +"\n" +"A(z) %s nem létezik" + +#: git-gui.sh:1788 lib/choose_repository.tcl:32 +msgid "Repository" +msgstr "Repó" + +#: git-gui.sh:1789 +msgid "Edit" +msgstr "Szerkesztés" + +#: git-gui.sh:1791 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "Branch" + +#: git-gui.sh:1794 lib/choose_rev.tcl:547 +#, fuzzy +msgid "Commit@@noun" +msgstr "Commit" + +#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "Merge" + +#: git-gui.sh:1798 lib/choose_rev.tcl:556 +#, fuzzy +msgid "Remote" +msgstr "Távoli:" + +#: git-gui.sh:1807 +msgid "Browse Current Branch's Files" +msgstr "A jelenlegi branch fájljainak böngészése" + +#: git-gui.sh:1811 +msgid "Browse Branch Files..." +msgstr "A branch fájljainak böngészése..." + +#: git-gui.sh:1816 +msgid "Visualize Current Branch's History" +msgstr "A jelenlegi branch történetének vizualizálása" + +#: git-gui.sh:1820 +msgid "Visualize All Branch History" +msgstr "Az összes branch történetének vizualizálása" + +#: git-gui.sh:1827 +#, tcl-format +msgid "Browse %s's Files" +msgstr "A(z) %s branch fájljainak böngészése" + +#: git-gui.sh:1829 +#, tcl-format +msgid "Visualize %s's History" +msgstr "A(z) %s branch történetének vizualizálása" + +#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Adatbázis statisztikák" + +#: git-gui.sh:1837 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Adatbázis tömörÃtése" + +#: git-gui.sh:1840 +msgid "Verify Database" +msgstr "Adatbázis ellenÅ‘rzése" + +#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 +#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +msgid "Create Desktop Icon" +msgstr "Asztal ikon létrehozása" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +msgid "Quit" +msgstr "Kilépés" + +#: git-gui.sh:1867 +msgid "Undo" +msgstr "Visszavonás" + +#: git-gui.sh:1870 +msgid "Redo" +msgstr "Mégis" + +#: git-gui.sh:1874 git-gui.sh:2366 +msgid "Cut" +msgstr "Kivágás" + +#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "Másolás" + +#: git-gui.sh:1880 git-gui.sh:2372 +msgid "Paste" +msgstr "Beillesztés" + +#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Törlés" + +#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +msgid "Select All" +msgstr "Mindent kiválaszt" + +#: git-gui.sh:1896 +msgid "Create..." +msgstr "Létrehozás..." + +#: git-gui.sh:1902 +msgid "Checkout..." +msgstr "Checkout..." + +#: git-gui.sh:1908 +msgid "Rename..." +msgstr "Ãtnevezés..." + +#: git-gui.sh:1913 git-gui.sh:2012 +msgid "Delete..." +msgstr "Törlés..." + +#: git-gui.sh:1918 +msgid "Reset..." +msgstr "VisszaállÃtás..." + +#: git-gui.sh:1930 git-gui.sh:2313 +msgid "New Commit" +msgstr "Új commit" + +#: git-gui.sh:1938 git-gui.sh:2320 +msgid "Amend Last Commit" +msgstr "Utolsó commit javÃtása" + +#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Keresés újra" + +#: git-gui.sh:1953 +msgid "Stage To Commit" +msgstr "Kiválasztás commitolásra" + +#: git-gui.sh:1958 +msgid "Stage Changed Files To Commit" +msgstr "MódosÃtott fájlok kiválasztása commitolásra" + +#: git-gui.sh:1964 +msgid "Unstage From Commit" +msgstr "Commitba való kiválasztás visszavonása" + +#: git-gui.sh:1969 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "Változtatások visszaállÃtása" + +#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +msgid "Sign Off" +msgstr "AláÃr" + +#: git-gui.sh:1980 git-gui.sh:2296 +#, fuzzy +msgid "Commit@@verb" +msgstr "Commit" + +#: git-gui.sh:1991 +msgid "Local Merge..." +msgstr "Helyi merge..." + +#: git-gui.sh:1996 +msgid "Abort Merge..." +msgstr "Merge megszakÃtása..." + +#: git-gui.sh:2008 +msgid "Push..." +msgstr "Push..." + +#: git-gui.sh:2019 lib/choose_repository.tcl:41 +msgid "Apple" +msgstr "Apple" + +#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "Névjegy: %s" + +#: git-gui.sh:2026 +msgid "Preferences..." +msgstr "" + +#: git-gui.sh:2034 git-gui.sh:2558 +msgid "Options..." +msgstr "Opciók..." + +#: git-gui.sh:2040 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "SegÃtség" + +#: git-gui.sh:2081 +msgid "Online Documentation" +msgstr "Online dokumentáció" + +#: git-gui.sh:2165 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" + +#: git-gui.sh:2198 +msgid "Current Branch:" +msgstr "Jelenlegi branch:" + +#: git-gui.sh:2219 +#, fuzzy +msgid "Staged Changes (Will Commit)" +msgstr "Kiválasztott változtatások (commitolva lesz)" + +#: git-gui.sh:2239 +#, fuzzy +msgid "Unstaged Changes" +msgstr "Változtatások kiválasztása" + +#: git-gui.sh:2286 +msgid "Stage Changed" +msgstr "Változtatások kiválasztása" + +#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Push" + +#: git-gui.sh:2332 +msgid "Initial Commit Message:" +msgstr "Kezdeti commit üzenet:" + +#: git-gui.sh:2333 +msgid "Amended Commit Message:" +msgstr "JavÃtó commit üzenet:" + +#: git-gui.sh:2334 +msgid "Amended Initial Commit Message:" +msgstr "Kezdeti javÃtó commit üzenet:" + +#: git-gui.sh:2335 +msgid "Amended Merge Commit Message:" +msgstr "JavÃtó merge commit üzenet:" + +#: git-gui.sh:2336 +msgid "Merge Commit Message:" +msgstr "Merge commit üzenet:" + +#: git-gui.sh:2337 +msgid "Commit Message:" +msgstr "Commit üzenet:" + +#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +msgid "Copy All" +msgstr "Összes másolása" + +#: git-gui.sh:2406 lib/blame.tcl:104 +msgid "File:" +msgstr "Fájl:" + +#: git-gui.sh:2508 +msgid "Refresh" +msgstr "FrissÃtés" + +#: git-gui.sh:2529 +msgid "Apply/Reverse Hunk" +msgstr "Hunk alkalmazása/visszaállÃtása" + +#: git-gui.sh:2535 +msgid "Decrease Font Size" +msgstr "Font méret csökkentése" + +#: git-gui.sh:2539 +msgid "Increase Font Size" +msgstr "Fönt méret növelése" + +#: git-gui.sh:2544 +msgid "Show Less Context" +msgstr "Kevesebb környezet mutatása" + +#: git-gui.sh:2551 +msgid "Show More Context" +msgstr "Több környezet mutatása" + +#: git-gui.sh:2565 +msgid "Unstage Hunk From Commit" +msgstr "Hunk törlése commitból" + +#: git-gui.sh:2567 +msgid "Stage Hunk For Commit" +msgstr "Hunk kiválasztása commitba" + +#: git-gui.sh:2586 +msgid "Initializing..." +msgstr "Inicializálás..." + +#: git-gui.sh:2677 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" + +#: git-gui.sh:2707 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" + +#: git-gui.sh:2712 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - egy grafikus felület a Githez." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "Fájl nézÅ‘" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Commit:" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "Commit másolása" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "A(z) %s olvasása..." + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "" + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "" + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "" + +#: lib/blame.tcl:731 +#, fuzzy +msgid "Loading annotation..." +msgstr "A(z) %s betöltése..." + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "" + +#: lib/blame.tcl:791 +#, fuzzy +msgid "Committer:" +msgstr "Commit:" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "" + +#: lib/blame.tcl:916 +#, fuzzy +msgid "In File:" +msgstr "Fájl:" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Branch checkoutolása" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Checkout" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Mégsem" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "RevÃzió" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "Opciók" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "KövetÅ‘ branch letöltése" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Helyi branch leválasztása" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Branch létrehozása" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Új branch létrehozása" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 +msgid "Create" +msgstr "Létrehozás" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Branch neve" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Név:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "EgyeztetendÅ‘ követési branch név" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "A következÅ‘ revÃziótól" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "LétezÅ‘ branch frissÃtése" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "Nem" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Csak fast forward" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "VisszaállÃtás" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Checkout létrehozás után" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Válasszunk ki egy követÅ‘ branchet." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "A(z) %s követÅ‘ branch nem branch a távoli repóban." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Adjunk megy egy branch nevet." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "A(z) '%s' nem egy elfogadható branch név." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Branch törlése" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Helyi branch törlése" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Helyi branchek" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Csak már merge-ölt törlése" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Mindig (Ne legyen merge teszt.)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "A következÅ‘ branchek nem teljesen lettek merge-ölve ebbe: %s:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"A törölt branchek visszaállÃtása bonyolult. \n" +"\n" +" Biztosan törli a kiválasztott brancheket?" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Nem sikerült törölni a következÅ‘ brancheket:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Branch átnevezése" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Ãtnevezés" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Branch:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Új név:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Válasszunk ki egy átnevezendÅ‘ branchet." + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "A(z) '%s' branch már létezik." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Nem sikerült átnevezni: '%s'." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "IndÃtás..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "Fájl böngészÅ‘" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "A(z) %s betöltése..." + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "[Fel a szülÅ‘höz]" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "A branch fájljainak böngészése" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:215 +#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 +#: lib/choose_repository.tcl:811 +msgid "Browse" +msgstr "Böngészés" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "A(z) %s letöltése innen: %s" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "Bezárás" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "A(z) '%s' branch nem létezik." + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"A(z) '%s' branch már létezik.\n" +"\n" +"Nem lehet fast-forwardolni a következÅ‘höz: %s.\n" +"Egy merge szükséges." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "A(z) '%s' merge strategy nem támogatott." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Nem sikerült frissÃteni a következÅ‘t: '%s'." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "A kiválasztási terület (index) már zárolva van." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Az utolsó keresési állapot nem egyezik meg a repó állpotával.\n" +"\n" +"Egy másik Git program módosÃtotta ezt a repót az utolsó keresés óta. Egy " +"újrakeresés mindenképpen szükséges mielÅ‘tt a jelenlegi branchet módosÃtani " +"lehetne.\n" +"\n" +"Az újrakeresés most automatikusan el fog indulni.\n" + +#: lib/checkout_op.tcl:322 +#, fuzzy, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Nincs munkakönyvtár" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "A(z) '%s' checkoutja megszakÃtva (fájlszintű merge-ölés szükséges)." + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "Fájlszintű merge-ölés szükséges." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Jelenleg a(z) '%s' branchen." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Már nem egy helyi branchen vagyunk.\n" +"\n" +"Ha egy branchen szeretnénk lenni, hozzunk létre egyet az 'Ez a leválasztott " +"checkout'-ból." + +#: lib/checkout_op.tcl:446 +#, fuzzy, tcl-format +msgid "Checked out '%s'." +msgstr "Checkout..." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "" +"A(z) '%s' -> '%s' visszaállÃtás a következÅ‘ commitok elvesztését jelenti:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "Az elveszett commitok helyreállÃtása nem biztos, hogy egyszerű." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "VisszaállÃtjuk a következÅ‘t: '%s'?" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "Vizualizálás" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Nem sikerült beállÃtani a jelenlegi branchet.\n" +"\n" +"A munkakönyvtár csak részben váltott át. A fájlok sikeresen frissÃtve " +"lettek, de nem sikerült frissÃteni egy belsÅ‘ Git fájlt.\n" +"\n" +"Ennek nem szabad megtörténnie. A(z) %s most kilép és feladja." + +#: lib/choose_font.tcl:39 +#, fuzzy +msgid "Select" +msgstr "Mindent kiválaszt" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "" + +#: lib/choose_font.tcl:73 +#, fuzzy +msgid "Font Size" +msgstr "Font méret csökkentése" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" + +#: lib/choose_repository.tcl:25 +msgid "Git Gui" +msgstr "" + +#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 +#, fuzzy +msgid "Create New Repository" +msgstr "Forrás repó" + +#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 +#, fuzzy +msgid "Clone Existing Repository" +msgstr "Cél repó" + +#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 +#, fuzzy +msgid "Open Existing Repository" +msgstr "Cél repó" + +#: lib/choose_repository.tcl:91 +msgid "Next >" +msgstr "" + +#: lib/choose_repository.tcl:152 +#, fuzzy, tcl-format +msgid "Location %s already exists." +msgstr "A(z) '%s' branch már létezik." + +#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 +#: lib/choose_repository.tcl:172 +#, fuzzy, tcl-format +msgid "Failed to create repository %s:" +msgstr "Nem sikerült teljesen elmenteni a beállÃtásokat:" + +#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +msgid "Directory:" +msgstr "" + +#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 +#: lib/choose_repository.tcl:834 +#, fuzzy +msgid "Git Repository" +msgstr "Repó" + +#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#, fuzzy, tcl-format +msgid "Directory %s already exists." +msgstr "A(z) '%s' branch már létezik." + +#: lib/choose_repository.tcl:265 +#, fuzzy, tcl-format +msgid "File %s already exists." +msgstr "A(z) '%s' branch már létezik." + +#: lib/choose_repository.tcl:286 +#, fuzzy +msgid "Clone" +msgstr "Bezárás" + +#: lib/choose_repository.tcl:299 +msgid "URL:" +msgstr "" + +#: lib/choose_repository.tcl:319 +msgid "Clone Type:" +msgstr "" + +#: lib/choose_repository.tcl:325 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "" + +#: lib/choose_repository.tcl:331 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "" + +#: lib/choose_repository.tcl:337 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "" + +#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 +#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 +#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 +#, fuzzy, tcl-format +msgid "Not a Git repository: %s" +msgstr "Nincs kiválasztott repó." + +#: lib/choose_repository.tcl:405 +msgid "Standard only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:409 +msgid "Shared only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:439 +msgid "Failed to configure origin" +msgstr "" + +#: lib/choose_repository.tcl:451 +msgid "Counting objects" +msgstr "" + +#: lib/choose_repository.tcl:452 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:476 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "" + +#: lib/choose_repository.tcl:512 +#, fuzzy, tcl-format +msgid "Nothing to clone from %s." +msgstr "Új változások letöltése innen: %s" + +#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:740 +msgid "The 'master' branch has not been initialized." +msgstr "" + +#: lib/choose_repository.tcl:527 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "" + +#: lib/choose_repository.tcl:539 +#, fuzzy, tcl-format +msgid "Cloning from %s" +msgstr "A(z) %s letöltése innen: %s" + +#: lib/choose_repository.tcl:570 +#, fuzzy +msgid "Copying objects" +msgstr "Az objektum adatbázis tömörÃtése" + +#: lib/choose_repository.tcl:571 +msgid "KiB" +msgstr "" + +#: lib/choose_repository.tcl:595 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "" + +#: lib/choose_repository.tcl:605 +msgid "Linking objects" +msgstr "" + +#: lib/choose_repository.tcl:606 +msgid "objects" +msgstr "" + +#: lib/choose_repository.tcl:614 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "" + +#: lib/choose_repository.tcl:669 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:680 +msgid "Cannot fetch tags. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:704 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:713 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "" + +#: lib/choose_repository.tcl:719 +#, fuzzy +msgid "Clone failed." +msgstr "A félbeszakÃtás nem sikerült." + +#: lib/choose_repository.tcl:726 +msgid "No default branch obtained." +msgstr "" + +#: lib/choose_repository.tcl:737 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "" + +#: lib/choose_repository.tcl:749 +#, fuzzy +msgid "Creating working directory" +msgstr "Nincs munkakönyvtár" + +#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "" + +#: lib/choose_repository.tcl:779 +msgid "Initial file checkout failed." +msgstr "" + +#: lib/choose_repository.tcl:795 +msgid "Open" +msgstr "" + +#: lib/choose_repository.tcl:805 +#, fuzzy +msgid "Repository:" +msgstr "Repó" + +#: lib/choose_repository.tcl:854 +#, fuzzy, tcl-format +msgid "Failed to open repository %s:" +msgstr "Nem sikerült teljesen elmenteni a beállÃtásokat:" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "Ez a leválasztott checkout" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "RevÃzió kifejezés:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Helyi branch" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "KövetÅ‘ branch" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "Tag" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Érvénytelen revÃzió: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Nincs kiválasztva revÃzió." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "A revÃzió kifejezés üres." + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Nincs semmi javÃtanivaló.\n" +"\n" +"Az elsÅ‘ commit létrehozása elÅ‘tt nincs semmilyen commit amit javitani " +"lehetne.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Nem lehet javÃtani merge alatt.\n" +"\n" +"A jelenlegi merge még nem teljesen fejezÅ‘dött be. Csak akkor javÃthat egy " +"elÅ‘bbi commitot, hogyha megszakÃtja a jelenlegi merge folyamatot.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Hiba a javÃtandó commit adat betöltése közben:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Nem sikerült megállapÃtani az azonosÃtót:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Érvénytelen GIT_COMMITTER_IDENT:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Az utolsó keresési állapot nem egyezik meg a repó állapotával.\n" +"\n" +"Egy másik Git program módosÃtotta ezt a repót az utolsó keresés óta. Egy " +"újrakeresés mindenképpen szükséges mielÅ‘tt a jelenlegi branchet módosÃtani " +"lehetne.\n" +"\n" +"Az újrakeresés most automatikusan el fog indulni.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Nem commitolhatunk fájlokat merge elÅ‘tt.\n" +"\n" +"A(z) %s fájlban ütközések vannak. Egyszer azokat ki kell javÃtani, majd " +"hozzá ki kell választani a fájlt mielÅ‘tt commitolni lehetne.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Ismeretlen fájl tÃpus %s érzékelve.\n" +"\n" +"A(z) %s fájlt nem tudja ez a program commitolni.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Nincs commitolandó változtatás.\n" +"\n" +"Legalább egy fájl ki kell választani, hogy commitolni lehessen.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Adjunk megy egy commit üzenetet.\n" +"\n" +"Egy jó commit üzenetnek a következÅ‘ a formátuma:\n" +"\n" +"- ElsÅ‘ sor: Egy mondatban leÃrja, hogy mit csináltunk.\n" +"- Második sor: Ãœres\n" +"- A többi sor: LeÃrja, hogy miért jó ez a változtatás.\n" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "a write-tree sikertelen:" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Nincs commitolandó változtatás.\n" +"\n" +"Egyetlen fájlt se módosÃtott ez a commit és merge commit se volt.\n" +"\n" +"Az újrakeresés most automatikusan el fog indulni.\n" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "Nincs commitolandó változtatás." + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "a commit-tree sikertelen:" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "az update-ref sikertelen:" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Létrejött a %s commit: %s" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "Munka folyamatban.. Várjunk..." + +#: lib/console.tcl:183 +msgid "Success" +msgstr "Siker" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "Hiba: a parancs sikertelen" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Elvesztett objektumok száma" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Elveszett objektumok által elfoglalt lemezterület" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Csomagolt objektumok számra" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Csomagok száma" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "A csomagolt objektumok által használt lemezterület" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "EltávolÃtásra váró csomagolt objektumok számra" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "Hulladék fájlok" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Az objektum adatbázis tömörÃtése" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Az objektum adatbázis ellenÅ‘rzése az fsck-objects használatával" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" + +#: lib/date.tcl:25 +#, fuzzy, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Érvénytelen revÃzió: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Nincsenek változások.\n" +"\n" +"A(z) %s módosÃtatlan.\n" +"\n" +"A fájl módosÃtási dátumát frissÃtette egy másik alkalmazás, de a fájl " +"tartalma változatlan.\n" +"\n" +"Egy újrakeresés fog indulni a hasonló állapotú fájlok megtalálása érdekében." + +#: lib/diff.tcl:81 +#, fuzzy, tcl-format +msgid "Loading diff of %s..." +msgstr "A(z) %s betöltése..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Hiba a fájl betöltése közben:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Hiba a diff betöltése közben:" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "Nem visszavonni a hunk kiválasztását." + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "Nem sikerült kiválasztani a hunkot." + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "hiba" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "figyelmeztetés" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "Ki kell javÃtanunk a fenti hibákat commit elÅ‘tt." + +#: lib/index.tcl:241 +#, fuzzy, tcl-format +msgid "Unstaging %s from commit" +msgstr "Commitba való kiválasztás visszavonása" + +#: lib/index.tcl:285 +#, fuzzy, tcl-format +msgid "Adding %s" +msgstr "A(z) %s olvasása..." + +#: lib/index.tcl:340 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "VisszaállÃtja a változtatásokat a(z) %s fájlban?" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "VisszaállÃtja a változtatásokat ebben e %i fájlban?" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Minden nem kiválasztott változtatás el fog veszni ezáltal a visszaállÃtás " +"által." + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "Ne csináljunk semmit" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"JavÃtás közben nem lehetséges a merge.\n" +"\n" +"Egyszer be kell fejezni ennek a commitnak a javÃtását, majd kezdÅ‘dhet egy " +"merge.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Az utolsó keresési állapot nem egyezik meg a repó állapotával.\n" +"\n" +"Egy másik Git program módosÃtotta ezt a repót az utolsó keresés óta. Egy " +"újrakeresés mindenképpen szükséges mielÅ‘tt a jelenlegi branchet módosÃtani " +"lehetne.\n" +"\n" +"Az újrakeresés most automatikusan el fog indulni.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Jelenleg egy ütközés feloldása közben vagyunk.\n" +"\n" +"A(z) %s fájlban ütközések vannak.\n" +"\n" +"Fel kell oldanunk Å‘ket, kiválasztani a fájlt, és commitolni hogy befejezzük " +"a jelenlegi merge-t. Csak ezután kezdhetünk el egy újabbat.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Jelenleg egy változtatás közben vagyunk.\n" +"\n" +"A(z) %s fájl megváltozott.\n" +"\n" +"ElÅ‘ször be kell fejeznünk a jelenlegi commitot, hogy elkezdhessünk egy merge-" +"t. Ez segÃteni fog, hogy félbeszakÃthassunk egy merge-t.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s / %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "A(z) %s és a(z) %s merge-ölése" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "A merge sikeresen befejezÅ‘dött." + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "A merge sikertelen. Fel kell oldanunk az ütközéseket." + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "Merge-ölés a következÅ‘be: %s" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "Merge-ölni szándékozott revÃzió" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"A commit javÃtás közben megszakÃtva.\n" +"\n" +"Be kell fejeznünk ennek a commitnak a javÃtását.\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"MegszakÃtjuk a merge-t?\n" +"\n" +"A jelenlegi merge megszakÃtása *MINDEN* nem commitolt változtatás " +"elvesztését jelenti.\n" +"\n" +"Folytatjuk a jelenlegi merge megszakÃtását?" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Visszavonjuk a módosÃtásokat?\n" +"\n" +"A módosÃtások visszavonása *MINDEN* nem commitolt változtatás elvesztését " +"jelenti.\n" +"\n" +"Folytatjuk a jelenlegi módosÃtások visszavonását?" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "FélbeszakÃtás" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "A félbeszakÃtás nem sikerült." + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "A megkeszakÃtás befejezÅ‘dött. Kész." + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "Alapértelmezés visszaállÃtása" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "Mentés" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "%s Repó" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "Globális (minden repó)" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "Felhasználónév" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "Email cÃm" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "A merge commitok összegzése" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "Merge beszédesség" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "Diffstat mutatása merge után" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "A fájl módosÃtási dátumok megbÃzhatóak" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "A követÅ‘ branchek eltávolÃtása letöltés alatt" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "A követÅ‘ branchek egyeztetése" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "A diff környezeti sorok száma" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "Új branch név sablon" + +#: lib/option.tcl:176 +#, fuzzy +msgid "Change Font" +msgstr "FÅ‘ betűtÃpus" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "" + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "Nem sikerült teljesen elmenteni a beállÃtásokat:" + +#: lib/remote.tcl:165 +#, fuzzy +msgid "Prune from" +msgstr "Törlés innen: %s..." + +# tcl-format +#: lib/remote.tcl:170 +#, fuzzy +msgid "Fetch from" +msgstr "Letöltés innen: %s..." + +#: lib/remote.tcl:213 +#, fuzzy +msgid "Push to" +msgstr "Push" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Távoli branch törlése" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Forrás repó" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "Távoli:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "TetszÅ‘leges URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Branchek" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Törlés csak akkor ha" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Merge-ölt a következÅ‘be:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Mindig (Ne végezzen merge vizsgálatokat)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Egy branch szükséges a 'Merge-ölt a következÅ‘be'-hez." + +#: lib/remote_branch_delete.tcl:184 +#, fuzzy, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "A következÅ‘ branchek nem teljesen lettek merge-ölve ebbe: %s:" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Egy vagy több merge teszt hibát jelzett, mivel nem töltöttük le a megfelelÅ‘ " +"commitokat. Próbáljunk meg letölteni a következÅ‘bÅ‘l: %s elÅ‘ször." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Válasszunk ki egy vagy több branchet törlésre." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"A törölt branchek visszaállÃtása nehéz.\n" +"\n" +"Töröljük a kiválasztott brancheket?" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Brancek törlése innen: %s" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Nincs kiválasztott repó." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "Keresés itt: %s..." + +#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 +msgid "Cannot write script:" +msgstr "Nem sikerült Ãrni a scriptet:" + +#: lib/shortcut.tcl:149 +msgid "Cannot write icon:" +msgstr "Nem sikerült Ãrni az ikont:" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i / %*i %s (%3i%%)" + +#: lib/transport.tcl:6 +#, fuzzy, tcl-format +msgid "fetch %s" +msgstr "Letöltés" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Új változások letöltése innen: %s" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "A %s repóból törölt követÅ‘ branchek törlése" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Változások pusholása ide: %s" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Pusholás: %s %s, ide: %s" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Branchek pusholása" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "Forrás branchek" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Cél repó" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "Ãtviteli opciók" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Vékony csomagok használata (lassú hálózati kapcsolatok számára)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Tageket is" + +#~ msgid "Cannot find the git directory:" +#~ msgstr "Nem található a git könyvtár:" + +#~ msgid "Unstaged Changes (Will Not Be Committed)" +#~ msgstr "Nem kiválasztott változtatások (nem lesz commitolva)" + +#~ msgid "Push to %s..." +#~ msgstr "Pusholás ide: %s..." + +#~ msgid "Add To Commit" +#~ msgstr "Hozzáadás a commithoz" + +#~ msgid "Add Existing To Commit" +#~ msgstr "Hozzáadás létezÅ‘ commithoz" + +#~ msgid "Running miga..." +#~ msgstr "A miga futtatása..." + +#~ msgid "Add Existing" +#~ msgstr "LétezÅ‘ hozzáadása" + +#~ msgid "" +#~ "Abort commit?\n" +#~ "\n" +#~ "Aborting the current commit will cause *ALL* uncommitted changes to be " +#~ "lost.\n" +#~ "\n" +#~ "Continue with aborting the current commit?" +#~ msgstr "" +#~ "MegszakÃtjuk a commitot?\n" +#~ "\n" +#~ "A jelenlegi commit megszakÃtása *MINDEN* nem commitolt változtatás " +#~ "elvesztését jelenti.\n" +#~ "\n" +#~ "Folytatjuk a jelenlegi commit megszakÃtását?" + +#~ msgid "Aborting... please wait..." +#~ msgstr "MegszakÃtás... várjunk..." diff --git a/git-gui/po/it.po b/git-gui/po/it.po new file mode 100644 index 0000000000..7668414104 --- /dev/null +++ b/git-gui/po/it.po @@ -0,0 +1,1872 @@ +# Translation of git-gui to Italian +# Copyright (C) 2007 Shawn Pearce +# This file is distributed under the same license as the git-gui package. +# Paolo Ciarrocchi <paolo.ciarrocchi@gmail.com>, 2007 +# Michele Ballabio <barra_cuda@katamail.com>, 2007. +# +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-10 14:42+0200\n" +"PO-Revision-Date: 2007-10-10 15:27+0200\n" +"Last-Translator: Paolo Ciarrocchi <paolo.ciarrocchi@gmail.com>\n" +"Language-Team: Italian <tp@lists.linux.it>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "git-gui: errore grave" + +#: git-gui.sh:595 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Caratteri non validi specificati in %s:" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "Caratteri principali" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "Caratteri per confronti e terminale" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "Impossibile trovare git nel PATH" + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "Impossibile determinare la versione di Git:" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"La versione di GIT non può essere determinata.\n" +"\n" +"%s riporta che la versione è '%s'.\n" +"\n" +"%s richiede almeno Git 1.5.0 o superiore.\n" +"\n" +"Assumere che '%s' sia alla versione 1.5.0?\n" + +#: git-gui.sh:853 +msgid "Git directory not found:" +msgstr "Non trovo la directory di git: " + +#: git-gui.sh:860 +msgid "Cannot move to top of working directory:" +msgstr "Impossibile spostarsi sulla directory principale del progetto:" + +#: git-gui.sh:867 +msgid "Cannot use funny .git directory:" +msgstr "Impossibile usare una .git directory strana:" + +#: git-gui.sh:872 +msgid "No working directory" +msgstr "Nessuna directory di lavoro" + +#: git-gui.sh:1019 +msgid "Refreshing file status..." +msgstr "Controllo dello stato dei file in corso..." + +#: git-gui.sh:1084 +msgid "Scanning for modified files ..." +msgstr "Ricerca di file modificati in corso..." + +#: git-gui.sh:1259 lib/browser.tcl:245 +msgid "Ready." +msgstr "Pronto." + +#: git-gui.sh:1525 +msgid "Unmodified" +msgstr "Non modificato" + +#: git-gui.sh:1527 +msgid "Modified, not staged" +msgstr "Modificato, non preparato per una nuova revisione" + +#: git-gui.sh:1528 git-gui.sh:1533 +msgid "Staged for commit" +msgstr "Preparato per una nuova revisione" + +#: git-gui.sh:1529 git-gui.sh:1534 +msgid "Portions staged for commit" +msgstr "Parti preparate per una nuova revisione" + +#: git-gui.sh:1530 git-gui.sh:1535 +msgid "Staged for commit, missing" +msgstr "Preparato per una nuova revisione, mancante" + +#: git-gui.sh:1532 +msgid "Untracked, not staged" +msgstr "Non tracciato, non preparato per una nuova revisione" + +#: git-gui.sh:1537 +msgid "Missing" +msgstr "Mancante" + +#: git-gui.sh:1538 +msgid "Staged for removal" +msgstr "Preparato per la rimozione" + +#: git-gui.sh:1539 +msgid "Staged for removal, still present" +msgstr "Preparato alla rimozione, ancora presente" + +#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +msgid "Requires merge resolution" +msgstr "Richiede risoluzione dei conflitti" + +#: git-gui.sh:1579 +msgid "Starting gitk... please wait..." +msgstr "Avvio di gitk... attendere..." + +#: git-gui.sh:1588 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"Impossibile avviare gitk:\n" +"\n" +"%s non esiste" + +#: git-gui.sh:1788 lib/choose_repository.tcl:32 +msgid "Repository" +msgstr "Archivio" + +#: git-gui.sh:1789 +msgid "Edit" +msgstr "Modifica" + +#: git-gui.sh:1791 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "Ramo" + +#: git-gui.sh:1794 lib/choose_rev.tcl:547 +msgid "Commit@@noun" +msgstr "Revisione" + +#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "Fusione (Merge)" + +#: git-gui.sh:1798 lib/choose_rev.tcl:556 +msgid "Remote" +msgstr "Remoto" + +#: git-gui.sh:1807 +msgid "Browse Current Branch's Files" +msgstr "Esplora i file del ramo corrente" + +#: git-gui.sh:1811 +msgid "Browse Branch Files..." +msgstr "Esplora i file del ramo..." + +#: git-gui.sh:1816 +msgid "Visualize Current Branch's History" +msgstr "Visualizza la cronologia del ramo corrente" + +#: git-gui.sh:1820 +msgid "Visualize All Branch History" +msgstr "Visualizza la cronologia di tutti i rami" + +#: git-gui.sh:1827 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Esplora i file di %s" + +#: git-gui.sh:1829 +#, tcl-format +msgid "Visualize %s's History" +msgstr "Visualizza la cronologia di %s" + +#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Statistiche dell'archivio" + +#: git-gui.sh:1837 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Comprimi l'archivio" + +#: git-gui.sh:1840 +msgid "Verify Database" +msgstr "Verifica l'archivio" + +#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 +#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +msgid "Create Desktop Icon" +msgstr "Crea icona desktop" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +msgid "Quit" +msgstr "Esci" + +#: git-gui.sh:1867 +msgid "Undo" +msgstr "Annulla" + +#: git-gui.sh:1870 +msgid "Redo" +msgstr "Ripeti" + +#: git-gui.sh:1874 git-gui.sh:2366 +msgid "Cut" +msgstr "Taglia" + +#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "Copia" + +#: git-gui.sh:1880 git-gui.sh:2372 +msgid "Paste" +msgstr "Incolla" + +#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Elimina" + +#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +msgid "Select All" +msgstr "Seleziona tutto" + +#: git-gui.sh:1896 +msgid "Create..." +msgstr "Crea..." + +#: git-gui.sh:1902 +msgid "Checkout..." +msgstr "Attiva..." + +#: git-gui.sh:1908 +msgid "Rename..." +msgstr "Rinomina" + +#: git-gui.sh:1913 git-gui.sh:2012 +msgid "Delete..." +msgstr "Elimina..." + +#: git-gui.sh:1918 +msgid "Reset..." +msgstr "Ripristina..." + +#: git-gui.sh:1930 git-gui.sh:2313 +msgid "New Commit" +msgstr "Nuova revisione" + +#: git-gui.sh:1938 git-gui.sh:2320 +msgid "Amend Last Commit" +msgstr "Correggi l'ultima revisione" + +#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Analizza nuovamente" + +#: git-gui.sh:1953 +msgid "Stage To Commit" +msgstr "Prepara per una nuova revisione" + +#: git-gui.sh:1958 +msgid "Stage Changed Files To Commit" +msgstr "Prepara i file modificati per una nuova revisione" + +#: git-gui.sh:1964 +msgid "Unstage From Commit" +msgstr "Annulla preparazione" + +#: git-gui.sh:1969 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "Annulla modifiche" + +#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +msgid "Sign Off" +msgstr "Sign Off" + +#: git-gui.sh:1980 git-gui.sh:2296 +msgid "Commit@@verb" +msgstr "Nuova revisione" + +#: git-gui.sh:1991 +msgid "Local Merge..." +msgstr "Fusione locale..." + +#: git-gui.sh:1996 +msgid "Abort Merge..." +msgstr "Interrompi fusione..." + +#: git-gui.sh:2008 +msgid "Push..." +msgstr "Propaga..." + +#: git-gui.sh:2019 lib/choose_repository.tcl:41 +msgid "Apple" +msgstr "Apple" + +#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "Informazioni su %s" + +#: git-gui.sh:2026 +msgid "Preferences..." +msgstr "Preferenze..." + +#: git-gui.sh:2034 git-gui.sh:2558 +msgid "Options..." +msgstr "Opzioni..." + +#: git-gui.sh:2040 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "Aiuto" + +#: git-gui.sh:2081 +msgid "Online Documentation" +msgstr "Documentazione sul web" + +#: git-gui.sh:2165 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" +"errore grave: impossibile effettuare lo stat del path %s: file o directory " +"non trovata" + +#: git-gui.sh:2198 +msgid "Current Branch:" +msgstr "Ramo attuale:" + +#: git-gui.sh:2219 +msgid "Staged Changes (Will Commit)" +msgstr "Modifiche preparate (saranno nella nuova revisione)" + +#: git-gui.sh:2239 +msgid "Unstaged Changes" +msgstr "Modifiche non preparate" + +#: git-gui.sh:2286 +msgid "Stage Changed" +msgstr "Prepara modificati" + +#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Propaga (Push)" + +#: git-gui.sh:2332 +msgid "Initial Commit Message:" +msgstr "Messaggio di revisione iniziale:" + +#: git-gui.sh:2333 +msgid "Amended Commit Message:" +msgstr "Messaggio di revisione corretto:" + +#: git-gui.sh:2334 +msgid "Amended Initial Commit Message:" +msgstr "Messaggio iniziale di revisione corretto:" + +#: git-gui.sh:2335 +msgid "Amended Merge Commit Message:" +msgstr "Messaggio di fusione corretto:" + +#: git-gui.sh:2336 +msgid "Merge Commit Message:" +msgstr "Messaggio di fusione:" + +#: git-gui.sh:2337 +msgid "Commit Message:" +msgstr "Messaggio di revisione:" + +#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +msgid "Copy All" +msgstr "Copia tutto" + +#: git-gui.sh:2406 lib/blame.tcl:104 +msgid "File:" +msgstr "File:" + +#: git-gui.sh:2508 +msgid "Refresh" +msgstr "Rinfresca" + +#: git-gui.sh:2529 +msgid "Apply/Reverse Hunk" +msgstr "Applica/Inverti sezione" + +#: git-gui.sh:2535 +msgid "Decrease Font Size" +msgstr "Diminuisci dimensione caratteri" + +#: git-gui.sh:2539 +msgid "Increase Font Size" +msgstr "Aumenta dimensione caratteri" + +#: git-gui.sh:2544 +msgid "Show Less Context" +msgstr "Mostra meno contesto" + +#: git-gui.sh:2551 +msgid "Show More Context" +msgstr "Mostra più contesto" + +#: git-gui.sh:2565 +msgid "Unstage Hunk From Commit" +msgstr "Sezione non preparata per una nuova revisione" + +#: git-gui.sh:2567 +msgid "Stage Hunk For Commit" +msgstr "Prepara sezione per una nuova revisione" + +#: git-gui.sh:2586 +msgid "Initializing..." +msgstr "Inizializzazione..." + +#: git-gui.sh:2677 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"Possibili problemi con le variabili d'ambiente.\n" +"\n" +"Le seguenti variabili d'ambiente saranno probabilmente\n" +"ignorate da tutti i sottoprocessi di Git avviati\n" +"da %s:\n" +"\n" + +#: git-gui.sh:2707 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"Ciò è dovuto a un problema conosciuto\n" +"causato dall'eseguibile Tcl distribuito da Cygwin." + +#: git-gui.sh:2712 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"Una buona alternativa a %s\n" +"consiste nell'assegnare valori alle variabili di configurazione\n" +"user.name e user.email nel tuo file ~/.gitconfig personale.\n" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - un'interfaccia grafica per Git." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "Mostra file" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Revisione:" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "Copia revisione" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "Lettura di %s..." + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "Caricamento annotazioni per copie/spostamenti..." + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "linee annotate" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "Caricamento annotazioni per posizione originaria..." + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "Annotazione completata." + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "Caricamento annotazioni..." + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "Autore:" + +#: lib/blame.tcl:791 +msgid "Committer:" +msgstr "Revisione creata da:" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "File originario:" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "In origine da:" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "Nel file:" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "Copiato o spostato qui da:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Attiva ramo" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Attiva" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Annulla" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "Revisione" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "Opzioni" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Recupera duplicato locale di ramo remoto" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Stacca da ramo locale" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Crea ramo" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Crea nuovo ramo" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 +msgid "Create" +msgstr "Crea" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Nome del ramo" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Nome:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "Appaia nome del duplicato locale di ramo remoto" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Revisione iniziale" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Aggiorna ramo esistente:" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "No" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Solo fast forward" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "Ripristina" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Attiva dopo la creazione" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Scegliere un duplicato locale di ramo remoto" + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" +"Il duplicato locale del ramo remoto %s non è un ramo nell'archivio remoto." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Inserire un nome per il ramo." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "'%s' non è utilizzabile come nome di ramo." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Elimina ramo" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Elimina ramo locale" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Rami locali" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Cancella solo se fuso con un altro ramo" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Sempre (Non effettuare verifiche di fusione)." + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "I rami seguenti non sono stati fusi completamente in %s:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"Ricomporre rami cancellati può essere complicato. \n" +"\n" +" Eliminare i rami selezionati?" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Cancellazione rami fallita:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Rinomina ramo" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Rinomina" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Ramo:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Nuovo Nome:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Scegliere un ramo da rinominare." + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "Il ramo '%s' esiste già ." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Impossibile rinominare '%s'." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "Avvio in corso..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "File browser" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "Caricamento %s..." + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "[Directory superiore]" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "Esplora i file del ramo" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:215 +#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 +#: lib/choose_repository.tcl:811 +msgid "Browse" +msgstr "Sfoglia" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Recupero %s da %s" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "errore grave: impossibile risolvere %s" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "Chiudi" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Il ramo '%s' non esiste." + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"Il ramo '%s' esiste già .\n" +"\n" +"Non può effettuare un 'fast-forward' a %s.\n" +"E' necessaria una fusione." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "La strategia di fusione '%s' non è supportata." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Aggiornamento di '%s' fallito." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "L'area di preparazione per una nuova revisione (indice) è già bloccata." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n" +"\n" +"Un altro programma Git ha modificato questo archivio dall'ultima analisi. " +"Bisogna effettuare una nuova analisi prima di poter cambiare il ramo " +"corrente.\n" +"\n" +"La nuova analisi comincerà ora.\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Aggiornamento della directory di lavoro a '%s' in corso..." + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "Attivazione di '%s' fallita (richiesta una fusione a livello file)." + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "E' richiesta una fusione a livello file." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Si rimarrà sul ramo '%s'." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Non si è più su un ramo locale\n" +"\n" +"Se si vuole rimanere su un ramo, crearne uno ora a partire da 'Questa " +"revisione attiva staccata'." + +#: lib/checkout_op.tcl:446 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Attivazione di '%s' completata." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "" +"Ripristinare '%s' a '%s' comporterà la perdita delle seguenti revisioni:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "Ricomporre le revisioni perdute potrebbe non essere semplice." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "Ripristinare '%s'?" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "Visualizza" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Preparazione ramo corrente fallita.\n" +"\n" +"Questa directory di lavoro è stata convertita solo parzialmente. I file sono " +"stati aggiornati correttamente, ma l'aggiornamento di un file di Git ha " +"prodotto degli errori.\n" +"\n" +"Questo non sarebbe dovuto succedere. %s ora terminerà senza altre azioni." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Seleziona" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Famiglia di caratteri" + +#: lib/choose_font.tcl:73 +msgid "Font Size" +msgstr "Dimensione caratteri" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "Esempio caratteri" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Questo è un testo d'esempio.\n" +"Se ti piace questo testo, può essere il carattere giusto." + +#: lib/choose_repository.tcl:25 +msgid "Git Gui" +msgstr "Git Gui" + +#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 +msgid "Create New Repository" +msgstr "Crea nuovo archivio" + +#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 +msgid "Clone Existing Repository" +msgstr "Clona archivio esistente" + +#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 +msgid "Open Existing Repository" +msgstr "Apri archivio esistente" + +#: lib/choose_repository.tcl:91 +msgid "Next >" +msgstr "Successivo >" + +#: lib/choose_repository.tcl:152 +#, tcl-format +msgid "Location %s already exists." +msgstr "La posizione %s esiste già ." + +#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 +#: lib/choose_repository.tcl:172 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Impossibile creare l'archivio %s:" + +#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +msgid "Directory:" +msgstr "Directory:" + +#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 +#: lib/choose_repository.tcl:834 +msgid "Git Repository" +msgstr "Archivio Git" + +#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#, tcl-format +msgid "Directory %s already exists." +msgstr "La directory %s esiste già ." + +#: lib/choose_repository.tcl:265 +#, tcl-format +msgid "File %s already exists." +msgstr "Il file %s esiste già ." + +#: lib/choose_repository.tcl:286 +msgid "Clone" +msgstr "Clona" + +#: lib/choose_repository.tcl:299 +msgid "URL:" +msgstr "URL:" + +#: lib/choose_repository.tcl:319 +msgid "Clone Type:" +msgstr "Tipo di clone:" + +#: lib/choose_repository.tcl:325 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Standard (veloce, semi-ridondante, con hardlink)" + +#: lib/choose_repository.tcl:331 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "Copia completa (più lento, backup ridondante)" + +#: lib/choose_repository.tcl:337 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Shared (il più veloce, non raccomandato, nessun backup)" + +#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 +#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 +#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "%s non è un archivio Git." + +#: lib/choose_repository.tcl:405 +msgid "Standard only available for local repository." +msgstr "Standard è disponibile solo per archivi locali." + +#: lib/choose_repository.tcl:409 +msgid "Shared only available for local repository." +msgstr "Shared è disponibile solo per archivi locali." + +#: lib/choose_repository.tcl:439 +msgid "Failed to configure origin" +msgstr "Impossibile configurare origin" + +#: lib/choose_repository.tcl:451 +msgid "Counting objects" +msgstr "Calcolo oggetti" + +#: lib/choose_repository.tcl:452 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:476 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "Impossibile copiare oggetti/info/alternate: %s" + +#: lib/choose_repository.tcl:512 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Niente da clonare da %s." + +#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:740 +msgid "The 'master' branch has not been initialized." +msgstr "Il ramo 'master' non è stato inizializzato." + +#: lib/choose_repository.tcl:527 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "Impossibile utilizzare gli hardlink. Si ricorrerà alla copia." + +#: lib/choose_repository.tcl:539 +#, tcl-format +msgid "Cloning from %s" +msgstr "Clonazione da %s" + +#: lib/choose_repository.tcl:570 +msgid "Copying objects" +msgstr "Copia degli oggetti" + +#: lib/choose_repository.tcl:571 +msgid "KiB" +msgstr "KiB" + +#: lib/choose_repository.tcl:595 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Impossibile copiare oggetto: %s" + +#: lib/choose_repository.tcl:605 +msgid "Linking objects" +msgstr "Collegamento oggetti" + +#: lib/choose_repository.tcl:606 +msgid "objects" +msgstr "oggetti" + +#: lib/choose_repository.tcl:614 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Hardlink impossibile sull'oggetto: %s" + +#: lib/choose_repository.tcl:669 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" +"Impossibile recuperare rami e oggetti. Controllare i dettagli forniti dalla " +"console." + +#: lib/choose_repository.tcl:680 +msgid "Cannot fetch tags. See console output for details." +msgstr "" +"Impossibile recuperare le etichette. Controllare i dettagli forniti dalla " +"console." + +#: lib/choose_repository.tcl:704 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" +"Impossibile determinare HEAD. Controllare i dettagli forniti dalla console." + +#: lib/choose_repository.tcl:713 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "Impossibile ripulire %s" + +#: lib/choose_repository.tcl:719 +msgid "Clone failed." +msgstr "Clonazione fallita." + +#: lib/choose_repository.tcl:726 +msgid "No default branch obtained." +msgstr "Non è stato trovato un ramo predefinito." + +#: lib/choose_repository.tcl:737 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "Impossibile risolvere %s come una revisione." + +#: lib/choose_repository.tcl:749 +msgid "Creating working directory" +msgstr "Creazione directory di lavoro" + +#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "file" + +#: lib/choose_repository.tcl:779 +msgid "Initial file checkout failed." +msgstr "Attivazione iniziale impossibile." + +#: lib/choose_repository.tcl:795 +msgid "Open" +msgstr "Apri" + +#: lib/choose_repository.tcl:805 +msgid "Repository:" +msgstr "Archivio:" + +#: lib/choose_repository.tcl:854 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Impossibile accedere all'archivio %s:" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "Questa revisione attiva staccata" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Espressione di revisione:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Ramo locale" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Duplicato locale di ramo remoto" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "Etichetta" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Revisione non valida: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Nessuna revisione selezionata." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "L'espressione di revisione è vuota." + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "Aggiornato" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "URL" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Non c'è niente da correggere.\n" +"\n" +"Stai per creare la revisione iniziale. Non esiste una revisione " +"precedente da correggere.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Non è possibile effettuare una correzione durante una fusione.\n" +"\n" +"In questo momento si sta effettuando una fusione che non è stata del tutto " +"completata. Non puoi correggere la revisione precedente a meno che prima tu " +"non interrompa l'operazione di fusione in corso.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Errore durante il caricamento dei dati della revisione da correggere:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Impossibile ottenere la tua identità :" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "GIT_COMMITTER_IDENT non valida:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n" +"\n" +"Un altro programma Git ha modificato questo archivio dall'ultima analisi. " +"Bisogna effettuare una nuova analisi prima di poter creare una nuova " +"revisione.\n" +"\n" +"La nuova analisi comincerà ora.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Non è possibile creare una revisione con file non sottoposti a fusione.\n" +"\n" +"Il file %s presenta dei conflitti. Devi risolverli e preparare il file per " +"creare una nuova revisione prima di effettuare questa azione.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Stato di file %s sconosciuto.\n" +"\n" +"Questo programma non può creare una revisione contenente il file %s.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Nessuna modifica per la nuova revisione.\n" +"\n" +"Devi preparare per una nuova revisione almeno 1 file prima di effettuare questa " +"operazione.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Bisogna fornire un messaggio di revisione.\n" +"\n" +"Un buon messaggio di revisione ha il seguente formato:\n" +"\n" +"- Prima linea: descrivi in una frase ciò che hai fatto.\n" +"- Seconda linea: vuota.\n" +"- Terza linea: spiega a cosa serve la tua modifica.\n" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "write-tree fallito:" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "La revisione %s sembra essere corrotta" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Nessuna modifica per la nuova revisione.\n" +"\n" +"Questa revisione non modifica alcun file e non effettua alcuna fusione.\n" +"\n" +"Si procederà subito ad una nuova analisi.\n" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "Nessuna modifica per la nuova revisione." + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "attenzione: Tcl non supporta la codifica '%s'." + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "commit-tree fallito:" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "update-ref fallito:" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Creata revisione %s: %s" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "Elaborazione in corso... attendere..." + +#: lib/console.tcl:183 +msgid "Success" +msgstr "Successo" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "Errore: comando fallito" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Numero di oggetti slegati" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Spazio su disco utilizzato da oggetti slegati" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Numero di oggetti impacchettati" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Numero di pacchetti" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Spazio su disco utilizzato da oggetti impacchettati" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "Oggetti impacchettati che attendono la potatura" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "File inutili" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Compressione dell'archivio in corso" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Verifica dell'archivio con fsck-objects in corso" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"Questo archivio attualmente ha circa %i oggetti slegati.\n" +"\n" +"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio " +"quando sono presenti più di %i oggetti slegati.\n" +"\n" +"Comprimere l'archivio ora?" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Git ha restituito una data non valida: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Non sono state trovate differenze.\n" +"\n" +"%s non ha modifiche.\n" +"\n" +"La data di modifica di questo file è stata cambiata da un'altra " +"applicazione, ma il contenuto del file è rimasto invariato.\n" +"\n" +"Si procederà automaticamente ad una nuova analisi per trovare altri file che " +"potrebbero avere lo stesso stato." + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Caricamento delle differenze di %s..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "Impossibile visualizzare %s" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Errore nel caricamento del file:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Archivio Git (sottoprogetto)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* File binario (il contenuto non sarà mostrato)." + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Errore nel caricamento delle differenze:" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "La sezione scelta è ancora pronta per una nuova revisione." + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "Impossibile preparare la sezione scelta per una nuova revisione." + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "errore" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "attenzione" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "Bisogna correggere gli errori suddetti prima di creare una nuova revisione." + +#: lib/index.tcl:241 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "%s non farà parte della prossima revisione" + +#: lib/index.tcl:285 +#, tcl-format +msgid "Adding %s" +msgstr "Aggiunta di %s in corso" + +#: lib/index.tcl:340 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Annullare le modifiche nel file %s?" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Annullare le modifiche in questi %i file?" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Tutte le modifiche non preparate per una nuova revisione saranno perse per " +"sempre." + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "Non fare niente" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Non posso effettuare fusioni durante una correzione.\n" +"\n" +"Bisogna finire di correggere questa revisione prima di iniziare una " +"qualunque fusione.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n" +"\n" +"Un altro programma Git ha modificato questo archivio dall'ultima analisi." +"Bisogna effettuare una nuova analisi prima di poter effettuare una fusione.\n" +"\n" +"La nuova analisi comincerà ora.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Sei nel mezzo di una fusione con conflitti.\n" +"\n" +"Il file %s ha dei conflitti.\n" +"\n" +"Bisogna risolvere i conflitti, preparare il file per una nuova revisione ed " +"infine crearla per completare la fusione corrente. Solo a questo punto " +"potrai iniziare un'altra fusione.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Sei nel mezzo di una modifica.\n" +"\n" +"Il file %s è stato modificato.\n" +"\n" +"Bisogna completare la creazione della revisione corrente prima di iniziare una fusione. " +"In questo modo sarà più facile interrompere una fusione non riuscita, nel " +"caso ce ne fosse bisogno.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s di %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "Fusione di %s e %s in corso" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "Fusione completata con successo." + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "Fusione fallita. Bisogna risolvere i conflitti." + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "Fusione in %s" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "Revisione da fondere" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Interruzione impossibile durante una correzione.\n" +"\n" +"Bisogna finire di correggere questa revisione.\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Interrompere fusione?\n" +"\n" +"L'interruzione della fusione corrente causerà la perdita di *TUTTE* le " +"modifiche non ancora presenti nell'archivio.\n" +"\n" +"Continuare con l'interruzione della fusione corrente?" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Ripristinare la revisione corrente e annullare le modifiche?\n" +"\n" +"L'annullamento delle modifiche causerà la perdita di *TUTTE* le modifiche " +"non ancora presenti nell'archivio.\n" +"\n" +"Continuare con l'annullamento delle modifiche correnti?" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "Interruzione in corso" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "Interruzione fallita." + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "Interruzione completata. Pronto." + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "Ripristina predefiniti" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "Salva" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "Archivio di %s" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "Tutti gli archivi" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "Nome utente" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "Indirizzo Email" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "Riepilogo nelle revisioni di fusione" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "Prolissità della fusione" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "Mostra statistiche delle differenze dopo la fusione" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "Fidati delle date di modifica dei file" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "" +"Effettua potatura dei duplicati locali di rami remoti durante il recupero" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "Appaia duplicati locali di rami remoti" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "Numero di linee di contesto nelle differenze" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "Modello per il nome di un nuovo ramo" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "Cambia caratteri" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "Scegli %s" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "pt." + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "Preferenze" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "Impossibile salvare completamente le opzioni:" + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "Effettua potatura da" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Recupera da" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Propaga verso" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Cancella ramo remoto" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Da archivio" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "Remoto:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "URL specifico:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Rami" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Elimina solo se" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Fuso in:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Sempre (non verificare le fusioni)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Si richiede un ramo per 'Fuso in'." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"I rami seguenti non sono stati fusi completamente in %s:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Una o più verifiche di fusione sono fallite perché mancano le revisioni " +"necessarie. Prova prima a recuperarle da %s." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Scegliere uno o più rami da cancellare." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Ricomporre rami cancellati è difficile.\n" +"\n" +"Cancellare i rami selezionati?" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Cancellazione rami da %s" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Nessun archivio selezionato." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "Analisi in corso %s..." + +#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 +msgid "Cannot write script:" +msgstr "Impossibile scrivere script:" + +#: lib/shortcut.tcl:149 +msgid "Cannot write icon:" +msgstr "Impossibile scrivere icona:" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i di %*i %s (%3i%%)" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "recupera da %s" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Recupero nuove modifiche da %s" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "potatura remota di %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "Effettua potatura dei duplicati locali di rami remoti cancellati da %s" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "propaga verso %s" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Propagazione modifiche a %s" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Propagazione %s %s a %s" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Propaga rami" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "Rami di origine" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Archivio di destinazione" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "Opzioni di trasferimento" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "Sovrascrivi ramo esistente (alcune modifiche potrebbero essere perse)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Utilizza 'thin pack' (per connessioni lente)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Includi etichette" + diff --git a/git-gui/po/ja.po b/git-gui/po/ja.po new file mode 100644 index 0000000000..f3a547b1b9 --- /dev/null +++ b/git-gui/po/ja.po @@ -0,0 +1,1843 @@ +# Translation of git-gui to Japanese +# Copyright (C) 2007 Shawn Pearce +# This file is distributed under the same license as the git-gui package. +# ã—らã„ã— ãªãªã“ <nanako3@bluebottle.com>, 2007. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"PO-Revision-Date: 2007-10-31 16:23+0900\n" +"Last-Translator: ã—らã„ã— ãªãªã“ <nanako3@bluebottle.com>\n" +"Language-Team: Japanese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "git-gui: 致命的ãªã‚¨ãƒ©ãƒ¼" + +#: git-gui.sh:595 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "%s ã«ç„¡åŠ¹ãªãƒ•ã‚©ãƒ³ãƒˆãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™:" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "主フォント" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "diff/コンソール・フォント" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "PATH ä¸ã« git ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "Git ãƒãƒ¼ã‚¸ãƒ§ãƒ³åãŒç†è§£ã§ãã¾ã›ã‚“:" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Git ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒç¢ºèªã§ãã¾ã›ã‚“。\n" +"\n" +"%s ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '%s' ã¨ã®ã“ã¨ã§ã™ã€‚\n" +"\n" +"%s ã¯æœ€ä½Žã§ã‚‚ 1.5.0 ã‹ãれ以é™ã® Git ãŒå¿…è¦ã§ã™\n" +"\n" +"'%s' ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ 1.5.0 ã¨æ€ã£ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ\n" + +#: git-gui.sh:853 +msgid "Git directory not found:" +msgstr "Git ディレクトリãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“:" + +#: git-gui.sh:860 +msgid "Cannot move to top of working directory:" +msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æœ€ä¸Šä½ã«ç§»å‹•ã§ãã¾ã›ã‚“" + +#: git-gui.sh:867 +msgid "Cannot use funny .git directory:" +msgstr "変㪠.git ディレクトリã¯ä½¿ãˆã¾ã›ã‚“" + +#: git-gui.sh:872 +msgid "No working directory" +msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãŒã‚ã‚Šã¾ã›ã‚“" + +#: git-gui.sh:1019 +msgid "Refreshing file status..." +msgstr "ファイル状態を更新ã—ã¦ã„ã¾ã™â€¦" + +#: git-gui.sh:1084 +msgid "Scanning for modified files ..." +msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’スã‚ャンã—ã¦ã„ã¾ã™â€¦" + +#: git-gui.sh:1259 lib/browser.tcl:245 +msgid "Ready." +msgstr "準備完了" + +#: git-gui.sh:1525 +msgid "Unmodified" +msgstr "変更無ã—" + +#: git-gui.sh:1527 +msgid "Modified, not staged" +msgstr "変更ã‚ã‚Šã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" + +#: git-gui.sh:1528 git-gui.sh:1533 +msgid "Staged for commit" +msgstr "コミット予定済" + +#: git-gui.sh:1529 git-gui.sh:1534 +msgid "Portions staged for commit" +msgstr "部分的ã«ã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆ" + +#: git-gui.sh:1530 git-gui.sh:1535 +msgid "Staged for commit, missing" +msgstr "コミット予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«ç„¡ã—" + +#: git-gui.sh:1532 +msgid "Untracked, not staged" +msgstr "管ç†å¤–ã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" + +#: git-gui.sh:1537 +msgid "Missing" +msgstr "ファイル無ã—" + +#: git-gui.sh:1538 +msgid "Staged for removal" +msgstr "削除予定済" + +#: git-gui.sh:1539 +msgid "Staged for removal, still present" +msgstr "削除予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«æœªå‰Šé™¤" + +#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +msgid "Requires merge resolution" +msgstr "è¦ãƒžãƒ¼ã‚¸è§£æ±º" + +#: git-gui.sh:1579 +msgid "Starting gitk... please wait..." +msgstr "gitk ã‚’èµ·å‹•ä¸â€¦ãŠå¾…ã¡ä¸‹ã•ã„…" + +#: git-gui.sh:1588 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"gitk ã‚’èµ·å‹•ã§ãã¾ã›ã‚“:\n" +"\n" +"%s ãŒã‚ã‚Šã¾ã›ã‚“" + +#: git-gui.sh:1788 lib/choose_repository.tcl:32 +msgid "Repository" +msgstr "リãƒã‚¸ãƒˆãƒª" + +#: git-gui.sh:1789 +msgid "Edit" +msgstr "編集" + +#: git-gui.sh:1791 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "ブランãƒ" + +#: git-gui.sh:1794 lib/choose_rev.tcl:547 +msgid "Commit@@noun" +msgstr "コミット" + +#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "マージ" + +#: git-gui.sh:1798 lib/choose_rev.tcl:556 +msgid "Remote" +msgstr "リモート" + +#: git-gui.sh:1807 +msgid "Browse Current Branch's Files" +msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" + +#: git-gui.sh:1811 +msgid "Browse Branch Files..." +msgstr "ブランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る…" + +#: git-gui.sh:1816 +msgid "Visualize Current Branch's History" +msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" + +#: git-gui.sh:1820 +msgid "Visualize All Branch History" +msgstr "å…¨ã¦ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" + +#: git-gui.sh:1827 +#, tcl-format +msgid "Browse %s's Files" +msgstr "ブランム%s ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" + +#: git-gui.sh:1829 +#, tcl-format +msgid "Visualize %s's History" +msgstr "ブランム%s ã®å±¥æ´ã‚’見る" + +#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "データベース統計" + +#: git-gui.sh:1837 lib/database.tcl:34 +msgid "Compress Database" +msgstr "データベース圧縮" + +#: git-gui.sh:1840 +msgid "Verify Database" +msgstr "データベース検証" + +#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 +#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +msgid "Create Desktop Icon" +msgstr "デスクトップ・アイコンを作る" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +msgid "Quit" +msgstr "終了" + +#: git-gui.sh:1867 +msgid "Undo" +msgstr "å…ƒã«æˆ»ã™" + +#: git-gui.sh:1870 +msgid "Redo" +msgstr "ã‚„ã‚Šç›´ã—" + +#: git-gui.sh:1874 git-gui.sh:2366 +msgid "Cut" +msgstr "切りå–ã‚Š" + +#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "コピー" + +#: git-gui.sh:1880 git-gui.sh:2372 +msgid "Paste" +msgstr "貼り付ã‘" + +#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "削除" + +#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +msgid "Select All" +msgstr "å…¨ã¦é¸æŠž" + +#: git-gui.sh:1896 +msgid "Create..." +msgstr "作æˆâ€¦" + +#: git-gui.sh:1902 +msgid "Checkout..." +msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: git-gui.sh:1908 +msgid "Rename..." +msgstr "åå‰å¤‰æ›´â€¦" + +#: git-gui.sh:1913 git-gui.sh:2012 +msgid "Delete..." +msgstr "削除…" + +#: git-gui.sh:1918 +msgid "Reset..." +msgstr "リセット…" + +#: git-gui.sh:1930 git-gui.sh:2313 +msgid "New Commit" +msgstr "æ–°è¦ã‚³ãƒŸãƒƒãƒˆ" + +#: git-gui.sh:1938 git-gui.sh:2320 +msgid "Amend Last Commit" +msgstr "最新コミットを訂æ£" + +#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "å†ã‚¹ã‚ャン" + +#: git-gui.sh:1953 +msgid "Stage To Commit" +msgstr "コミット予定ã™ã‚‹" + +#: git-gui.sh:1958 +msgid "Stage Changed Files To Commit" +msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’コミット予定" + +#: git-gui.sh:1964 +msgid "Unstage From Commit" +msgstr "コミットã‹ã‚‰é™ã‚ã™" + +#: git-gui.sh:1969 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "変更を元ã«æˆ»ã™" + +#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +msgid "Sign Off" +msgstr "ç½²å" + +#: git-gui.sh:1980 git-gui.sh:2296 +msgid "Commit@@verb" +msgstr "コミット" + +#: git-gui.sh:1991 +msgid "Local Merge..." +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒžãƒ¼ã‚¸â€¦" + +#: git-gui.sh:1996 +msgid "Abort Merge..." +msgstr "マージä¸æ¢â€¦" + +#: git-gui.sh:2008 +msgid "Push..." +msgstr "プッシュ…" + +#: git-gui.sh:2019 lib/choose_repository.tcl:41 +msgid "Apple" +msgstr "ã‚Šã‚“ã”" + +#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "%s ã«ã¤ã„ã¦" + +#: git-gui.sh:2026 +msgid "Preferences..." +msgstr "è¨å®šâ€¦" + +#: git-gui.sh:2034 git-gui.sh:2558 +msgid "Options..." +msgstr "オプション…" + +#: git-gui.sh:2040 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "ヘルプ" + +#: git-gui.sh:2081 +msgid "Online Documentation" +msgstr "オンライン・ドã‚ュメント" + +#: git-gui.sh:2165 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "致命的: パス %s ㌠stat ã§ãã¾ã›ã‚“。ãã®ã‚ˆã†ãªãƒ•ã‚¡ã‚¤ãƒ«ã‚„ディレクトリã¯ã‚ã‚Šã¾ã›ã‚“" + +#: git-gui.sh:2198 +msgid "Current Branch:" +msgstr "ç¾åœ¨ã®ãƒ–ランãƒ" + +#: git-gui.sh:2219 +msgid "Staged Changes (Will Commit)" +msgstr "ステージングã•ã‚ŒãŸï¼ˆã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆã®ï¼‰å¤‰æ›´" + +#: git-gui.sh:2239 +msgid "Unstaged Changes" +msgstr "コミット予定ã«å…¥ã£ã¦ã„ãªã„変更" + +#: git-gui.sh:2286 +msgid "Stage Changed" +msgstr "変更をコミット予定ã«å…¥ã‚Œã‚‹" + +#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "プッシュ" + +#: git-gui.sh:2332 +msgid "Initial Commit Message:" +msgstr "最åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" + +#: git-gui.sh:2333 +msgid "Amended Commit Message:" +msgstr "訂æ£ã—ãŸã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" + +#: git-gui.sh:2334 +msgid "Amended Initial Commit Message:" +msgstr "訂æ£ã—ãŸæœ€åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" + +#: git-gui.sh:2335 +msgid "Amended Merge Commit Message:" +msgstr "訂æ£ã—ãŸãƒžãƒ¼ã‚¸ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" + +#: git-gui.sh:2336 +msgid "Merge Commit Message:" +msgstr "マージコミットメッセージ:" + +#: git-gui.sh:2337 +msgid "Commit Message:" +msgstr "コミットメッセージ:" + +#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +msgid "Copy All" +msgstr "å…¨ã¦ã‚³ãƒ”ー" + +#: git-gui.sh:2406 lib/blame.tcl:104 +msgid "File:" +msgstr "ファイル:" + +#: git-gui.sh:2508 +msgid "Refresh" +msgstr "å†èªã¿è¾¼ã¿" + +#: git-gui.sh:2529 +msgid "Apply/Reverse Hunk" +msgstr "パッãƒã‚’é©ç”¨/å–り消ã™" + +#: git-gui.sh:2535 +msgid "Decrease Font Size" +msgstr "フォントをå°ã•ã" + +#: git-gui.sh:2539 +msgid "Increase Font Size" +msgstr "フォントを大ãã" + +#: git-gui.sh:2544 +msgid "Show Less Context" +msgstr "文脈を少ãªã" + +#: git-gui.sh:2551 +msgid "Show More Context" +msgstr "文脈を多ã" + +#: git-gui.sh:2565 +msgid "Unstage Hunk From Commit" +msgstr "パッãƒã‚’コミット予定ã‹ã‚‰å¤–ã™" + +#: git-gui.sh:2567 +msgid "Stage Hunk For Commit" +msgstr "パッãƒã‚’コミット予定ã«åŠ ãˆã‚‹" + +#: git-gui.sh:2586 +msgid "Initializing..." +msgstr "åˆæœŸåŒ–ã—ã¦ã„ã¾ã™â€¦" + +#: git-gui.sh:2677 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"環境ã«å•é¡ŒãŒã‚ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™\n" +"\n" +"以下ã®ç’°å¢ƒå¤‰æ•°ã¯ %s ãŒèµ·å‹•ã™ã‚‹ Git サブプãƒã‚»ã‚¹ã«ã‚ˆã£ã¦ç„¡è¦–ã•ã‚Œã‚‹ã§ã—ょã†:\n" +"\n" + +#: git-gui.sh:2707 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"ã“れ㯠Cygwin ã§é…布ã•ã‚Œã¦ã„ã‚‹ Tcl ãƒã‚¤ãƒŠãƒªã«\n" +"é–¢ã—ã¦ã®æ—¢çŸ¥ã®å•é¡Œã«ã‚ˆã‚Šã¾ã™" + +#: git-gui.sh:2712 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"個人的㪠~/.gitconfig ファイル内㧠user.name 㨠user.email ã®å€¤ã‚’è¨å®š\n" +"ã™ã‚‹ã®ãŒã€%s ã®è‰¯ã„代用ã¨ãªã‚Šã¾ã™\n" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "Git ã®ã‚°ãƒ©ãƒ•ã‚£ã‚«ãƒ«UI git-gui" + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "ファイルピューワ" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "コミット:" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "コミットをコピー" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "%s ã‚’èªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "コピー・移動追跡データをèªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "行を注釈ã—ã¾ã—ãŸ" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "å…ƒä½ç½®è¡Œã®æ³¨é‡ˆãƒ‡ãƒ¼ã‚¿ã‚’èªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "注釈完了ã—ã¾ã—ãŸ" + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "注釈をèªã¿è¾¼ã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "作者:" + +#: lib/blame.tcl:791 +msgid "Committer:" +msgstr "コミット者:" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "元ファイル" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "原作者:" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "ファイル:" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "複写・移動者:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "ブランãƒã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "ä¸æ¢" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "リビジョン" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "オプション" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "トラッã‚ング・ブランãƒã‚’フェッãƒ" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‹ã‚‰å‰Šé™¤" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "ブランãƒã‚’作æˆ" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "ブランãƒã‚’æ–°è¦ä½œæˆ" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 +msgid "Create" +msgstr "作æˆ" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "ブランãƒå" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "åå‰:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "トラッã‚ング・ブランãƒåã‚’åˆã‚ã›ã‚‹" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "åˆæœŸãƒªãƒ“ジョン" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "æ—¢å˜ã®ãƒ–ランãƒã‚’æ›´æ–°:" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "ã„ã„ãˆ" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "æ—©é€ã‚Šã®ã¿" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "リセット" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "作æˆã—ã¦ã™ããƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "トラッã‚ング・ブランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "トラッã‚ング・ブランム%s ã¯é 隔リãƒã‚¸ãƒˆãƒªã®ãƒ–ランãƒã§ã¯ã‚ã‚Šã¾ã›ã‚“。" + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "ブランãƒåを指定ã—ã¦ä¸‹ã•ã„。" + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "'%s' ã¯ãƒ–ランãƒåã«ä½¿ãˆã¾ã›ã‚“。" + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "ブランãƒå‰Šé™¤" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‚’削除" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒ" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "マージ済ã¿ã®æ™‚ã®ã¿å‰Šé™¤" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "ç„¡æ¡ä»¶(マージテストã—ãªã„)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"ブランãƒã‚’削除ã™ã‚‹ã¨å…ƒã«æˆ»ã™ã®ã¯å›°é›£ã§ã™ã€‚ \n" +"\n" +" é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"以下ã®ãƒ–ランãƒã‚’削除ã§ãã¾ã›ã‚“:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "ブランãƒã®åå‰å¤‰æ›´" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "åå‰å¤‰æ›´" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "ブランãƒ:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "æ–°ã—ã„åå‰:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "åå‰ã‚’変更ã™ã‚‹ãƒ–ランãƒã‚’é¸ã‚“ã§ä¸‹ã•ã„。" + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "'%s'ã¨ã„ã†ãƒ–ランãƒã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "'%s'ã®åå‰å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "èµ·å‹•ä¸â€¦" + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "ファイル・ブラウザ" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "%s ã‚’ãƒãƒ¼ãƒ‰ä¸â€¦" + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "[上ä½ãƒ•ã‚©ãƒ«ãƒ€ã¸]" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:215 +#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 +#: lib/choose_repository.tcl:811 +msgid "Browse" +msgstr "ブラウズ" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "%s ã‹ã‚‰ %s をフェッãƒã—ã¦ã„ã¾ã™" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "致命的エラー: %s を解決ã§ãã¾ã›ã‚“" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "é–‰ã˜ã‚‹" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "ブランãƒ'%s'ã¯å˜åœ¨ã—ã¾ã›ã‚“。" + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"ブランム'%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚\n" +"\n" +"%s ã«æ—©é€ã‚Šã§ãã¾ã›ã‚“。\n" +"マージãŒå¿…è¦ã§ã™ã€‚" + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "'%s' マージ戦略ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“。" + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "'%s' ã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "インデックスã¯æ—¢ã«ãƒãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚" + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" +"\n" +"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚ç¾åœ¨" +"ã®ãƒ–ランãƒã‚’変更ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’ '%s' ã«æ›´æ–°ã—ã¦ã„ã¾ã™â€¦" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "'%s' ã®ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã‚’ä¸æ¢ã—ã¾ã—ãŸï¼ˆãƒ•ã‚¡ã‚¤ãƒ«æ¯Žã®ãƒžãƒ¼ã‚¸ãŒå¿…è¦ã§ã™ï¼‰ã€‚" + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "ファイル毎ã®ãƒžãƒ¼ã‚¸ãŒå¿…è¦ã§ã™ã€‚" + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "ブランム'%s' ã«æ»žã¾ã‚Šã¾ã™ã€‚" + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‹ã‚‰é›¢ã‚Œã¾ã™ã€‚\n" +"\n" +"ブランãƒä¸Šã«æ»žã¾ã‚ŠãŸã„ã¨ãã¯ã€ã“ã®ã€Œåˆ†é›¢ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã€ã‹ã‚‰æ–°è¦ãƒ–ラン" +"ãƒã‚’開始ã—ã¦ãã ã•ã„。" + +#: lib/checkout_op.tcl:446 +#, tcl-format +msgid "Checked out '%s'." +msgstr "'%s' ã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸ" + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "'%s' ã‚’ '%s' ã«ãƒªã‚»ãƒƒãƒˆã™ã‚‹ã¨ã€ä»¥ä¸‹ã®ã‚³ãƒŸãƒƒãƒˆãŒå¤±ãªã‚ã‚Œã¾ã™:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "失ãªã‚ã‚ŒãŸã‚³ãƒŸãƒƒãƒˆã‚’回復ã™ã‚‹ã®ã¯ç°¡å˜ã§ã¯ã‚ã‚Šã¾ã›ã‚“。" + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "'%s' をリセットã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "å¯è¦–化" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"ç¾åœ¨ã®ãƒ–ランãƒã‚’è¨å®šã§ãã¾ã›ã‚“。\n" +"\n" +"作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã¯éƒ¨åˆ†çš„ã«ã—ã‹åˆ‡ã‚Šæ›¿ã‚ã£ã¦ã„ã¾ã›ã‚“。ファイルã®æ›´æ–°ã«ã¯æˆåŠŸã—" +"ã¾ã—ãŸãŒã€ Git ã®å†…部データを更新ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚\n" +"èµ·ã“ã‚‹ã¯ãšã®ãªã„エラーã§ã™ã€‚ã‚ãらã‚㦠%s を終了ã—ã¾ã™ã€‚" + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "é¸æŠž" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "フォント・ファミリー" + +#: lib/choose_font.tcl:73 +msgid "Font Size" +msgstr "フォントã®å¤§ãã•" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "フォント・サンプル" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"ã“ã‚Œã¯ã‚µãƒ³ãƒ—ル文ã§ã™ã€‚\n" +"ã“ã®ãƒ•ã‚©ãƒ³ãƒˆãŒæ°—ã«å…¥ã‚Œã°ãŠä½¿ã„ã«ãªã‚Œã¾ã™ã€‚" + +#: lib/choose_repository.tcl:25 +msgid "Git Gui" +msgstr "Git GUI" + +#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 +msgid "Create New Repository" +msgstr "æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’作る" + +#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 +msgid "Clone Existing Repository" +msgstr "æ—¢å˜ãƒªãƒã‚¸ãƒˆãƒªã‚’複製ã™ã‚‹" + +#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 +msgid "Open Existing Repository" +msgstr "æ—¢å˜ãƒªãƒã‚¸ãƒˆãƒªã‚’é–‹ã" + +#: lib/choose_repository.tcl:91 +msgid "Next >" +msgstr "次 >" + +#: lib/choose_repository.tcl:152 +#, tcl-format +msgid "Location %s already exists." +msgstr "'%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + +#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 +#: lib/choose_repository.tcl:172 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "リãƒã‚¸ãƒˆãƒª %s を作製ã§ãã¾ã›ã‚“:" + +#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +msgid "Directory:" +msgstr "ディレクトリ:" + +#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 +#: lib/choose_repository.tcl:834 +msgid "Git Repository" +msgstr "GIT リãƒã‚¸ãƒˆãƒª" + +#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#, tcl-format +msgid "Directory %s already exists." +msgstr "ディレクトリ '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + +#: lib/choose_repository.tcl:265 +#, tcl-format +msgid "File %s already exists." +msgstr "ファイル '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + +#: lib/choose_repository.tcl:286 +msgid "Clone" +msgstr "複製" + +#: lib/choose_repository.tcl:299 +msgid "URL:" +msgstr "URL:" + +#: lib/choose_repository.tcl:319 +msgid "Clone Type:" +msgstr "複製方å¼:" + +#: lib/choose_repository.tcl:325 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "標準(高速・ä¸å†—長度・ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯)" + +#: lib/choose_repository.tcl:331 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "全複写(低速・冗長ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—)" + +#: lib/choose_repository.tcl:337 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "共有(最高速・éžæŽ¨å¥¨ãƒ»ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ç„¡ã—)" + +#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 +#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 +#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Git リãƒã‚¸ãƒˆãƒªã§ã¯ã‚ã‚Šã¾ã›ã‚“: %s" + +#: lib/choose_repository.tcl:405 +msgid "Standard only available for local repository." +msgstr "標準方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚" + +#: lib/choose_repository.tcl:409 +msgid "Shared only available for local repository." +msgstr "共有方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚" + +#: lib/choose_repository.tcl:439 +msgid "Failed to configure origin" +msgstr "origin ã‚’è¨å®šã§ãã¾ã›ã‚“ã§ã—ãŸ" + +#: lib/choose_repository.tcl:451 +msgid "Counting objects" +msgstr "オブジェクトを数ãˆã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:452 +msgid "buckets" +msgstr "ãƒã‚±ãƒ„" + +#: lib/choose_repository.tcl:476 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "objects/info/alternates を複写ã§ãã¾ã›ã‚“: %s" + +#: lib/choose_repository.tcl:512 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "%s ã‹ã‚‰è¤‡è£½ã™ã‚‹å†…容ã¯ã‚ã‚Šã¾ã›ã‚“" + +#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:740 +msgid "The 'master' branch has not been initialized." +msgstr "'master' ブランãƒãŒåˆæœŸåŒ–ã•ã‚Œã¦ã„ã¾ã›ã‚“" + +#: lib/choose_repository.tcl:527 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ãŒä½œã‚Œãªã„ã®ã§ã€ã‚³ãƒ”ーã—ã¾ã™" + +#: lib/choose_repository.tcl:539 +#, tcl-format +msgid "Cloning from %s" +msgstr "%s ã‹ã‚‰è¤‡è£½ã—ã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:570 +msgid "Copying objects" +msgstr "オブジェクトを複写ã—ã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:571 +msgid "KiB" +msgstr "KiB" + +#: lib/choose_repository.tcl:595 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "オブジェクトを複写ã§ãã¾ã›ã‚“: %s" + +#: lib/choose_repository.tcl:605 +msgid "Linking objects" +msgstr "オブジェクトを連çµã—ã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:606 +msgid "objects" +msgstr "オブジェクト" + +#: lib/choose_repository.tcl:614 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "オブジェクトをãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ã§ãã¾ã›ã‚“: %s" + +#: lib/choose_repository.tcl:669 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "ブランãƒã‚„オブジェクトをå–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" + +#: lib/choose_repository.tcl:680 +msgid "Cannot fetch tags. See console output for details." +msgstr "ã‚¿ã‚°ã‚’å–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" + +#: lib/choose_repository.tcl:704 +msgid "Cannot determine HEAD. See console output for details." +msgstr "HEAD を確定ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" + +#: lib/choose_repository.tcl:713 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "%s を掃除ã§ãã¾ã›ã‚“" + +#: lib/choose_repository.tcl:719 +msgid "Clone failed." +msgstr "複写ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" + +#: lib/choose_repository.tcl:726 +msgid "No default branch obtained." +msgstr "デフォールト・ブランãƒãŒå–å¾—ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ" + +#: lib/choose_repository.tcl:737 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "%s をコミットã¨ã—ã¦è§£é‡ˆã§ãã¾ã›ã‚“" + +#: lib/choose_repository.tcl:749 +msgid "Creating working directory" +msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’作æˆã—ã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "ファイル" + +#: lib/choose_repository.tcl:779 +msgid "Initial file checkout failed." +msgstr "åˆæœŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã«å¤±æ•—ã—ã¾ã—ãŸ" + +#: lib/choose_repository.tcl:795 +msgid "Open" +msgstr "é–‹ã" + +#: lib/choose_repository.tcl:805 +msgid "Repository:" +msgstr "リãƒã‚¸ãƒˆãƒª:" + +#: lib/choose_repository.tcl:854 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "リãƒã‚¸ãƒˆãƒª %s ã‚’é–‹ã‘ã¾ã›ã‚“:" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "分離ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "リビジョンå¼:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒ" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "トラッã‚ング・ブランãƒ" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "ã‚¿ã‚°" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "無効ãªãƒªãƒ“ジョン: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "リビジョンãŒæœªé¸æŠžã§ã™ã€‚" + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "リビジョンå¼ãŒç©ºã§ã™ã€‚" + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "æ›´æ–°ã—ã¾ã—ãŸ" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "URL" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆãŒãã‚‚ãã‚‚ã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"ã“ã‚Œã‹ã‚‰ä½œã‚‹ã®ã¯æœ€åˆã®ã‚³ãƒŸãƒƒãƒˆã§ã™ã€‚ãã®å‰ã«ã¯ã¾ã 訂æ£ã™ã‚‹ã‚ˆã†ãªã‚³ãƒŸãƒƒãƒˆã¯ã‚" +"ã‚Šã¾ã›ã‚“。\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"マージä¸ã«ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£ã¯ã§ãã¾ã›ã‚“。\n" +"\n" +"ç¾åœ¨ã¯ã¾ã マージã®é€”ä¸ã§ã™ã€‚å…ˆã«ã“ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ¢ã—ãªã„ã¨ã€å‰ã®ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£" +"ã¯ã§ãã¾ã›ã‚“\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ãƒ¼ã‚¿ã‚’èªã‚ã¾ã›ã‚“:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "ユーザã®æ£ä½“を確èªã§ãã¾ã›ã‚“:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "GIT_COMMITTER_IDENT ãŒç„¡åŠ¹ã§ã™:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" +"\n" +"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚æ–°ã—" +"ãコミットã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"マージã—ã¦ã„ãªã„ファイルã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" +"\n" +"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚ã¾ãšè§£æ±ºã—ã¦ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã‚‹å¿…" +"è¦ãŒã‚ã‚Šã¾ã™ã€‚\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"ä¸æ˜Žãªãƒ•ã‚¡ã‚¤ãƒ«çŠ¶æ…‹ %s ã§ã™ã€‚\n" +"\n" +"ファイル %s ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã§ã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"最低一ã¤ã®å¤‰æ›´ã‚’コミット予定ã«åŠ ãˆã¦ã‹ã‚‰ã‚³ãƒŸãƒƒãƒˆã—ã¦ä¸‹ã•ã„。\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"コミット・メッセージを入力ã—ã¦ä¸‹ã•ã„。\n" +"\n" +"æ£ã—ã„コミット・メッセージã¯:\n" +"\n" +"- 第1行: 何をã—ãŸã‹ã€ã‚’1行ã§è¦ç´„。\n" +"- 第2行: 空白\n" +"- 残りã®è¡Œ: ãªãœã€ã“ã®å¤‰æ›´ãŒè‰¯ã„変更ã‹ã€ã®èª¬æ˜Žã€‚\n" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "write-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "コミット %s ã¯å£Šã‚Œã¦ã„ã¾ã™" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"マージã§ãªãã€ã¾ãŸã€ä¸€ã¤ã‚‚変更点ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。" + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "è¦å‘Š: Tcl ã¯ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° '%s' をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "commit-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "update-ref ãŒå¤±æ•—ã—ã¾ã—ãŸ:" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "コミット %s を作æˆã—ã¾ã—ãŸ: %s" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "実行ä¸â€¦ãŠå¾…ã¡ä¸‹ã•ã„…" + +#: lib/console.tcl:183 +msgid "Success" +msgstr "æˆåŠŸ" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "エラー: コマンドãŒå¤±æ•—ã—ã¾ã—ãŸ" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®æ•°" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®æ•°" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "パックã®æ•°" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "パックã«å˜åœ¨ã™ã‚‹ã®ã§æ¨ã¦ã¦è‰¯ã„オブジェクトã®æ•°" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "ゴミファイル" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "データベース圧縮" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "fsck-objects ã§ã‚ªãƒ–ジェクト・データベースを検証ã—ã¦ã„ã¾ã™" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã¯ãŠãŠã‚ˆã %i 個ã®å€‹åˆ¥ã‚ªãƒ–ジェクトãŒã‚ã‚Šã¾ã™\n" +"\n" +"最é©ãªæ€§èƒ½ã‚’ä¿ã¤ãŸã‚ã«ã€%i 個以上ã®å€‹åˆ¥ã‚ªãƒ–ジェクトを作る毎ã«ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚’圧縮ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™\n" +"\n" +"データベースを圧縮ã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Git ã‹ã‚‰å‡ºãŸç„¡åŠ¹ãªæ—¥ä»˜: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"変更ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"%s ã«ã¯å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å¤‰æ›´æ™‚刻ã¯ä»–ã®ã‚¢ãƒ—リケーションã«ã‚ˆã£ã¦æ›´æ–°ã•ã‚Œã¦ã„ã¾ã™ãŒãƒ•ã‚¡ã‚¤" +"ル内容ã«ã¯å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"åŒæ§˜ãªçŠ¶æ…‹ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’探ã™ãŸã‚ã«ã€è‡ªå‹•çš„ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚" + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "%s ã®å¤‰æ›´ç‚¹ã‚’ãƒãƒ¼ãƒ‰ä¸â€¦" + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "%s を表示ã§ãã¾ã›ã‚“" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "ファイルをèªã‚€éš›ã®ã‚¨ãƒ©ãƒ¼ã§ã™:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Git リãƒã‚¸ãƒˆãƒª(サブプãƒã‚¸ã‚§ã‚¯ãƒˆ)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ«(内容ã¯è¡¨ç¤ºã—ã¾ã›ã‚“)" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "diff ã‚’èªã‚€éš›ã®ã‚¨ãƒ©ãƒ¼ã§ã™:" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã‹ã‚‰å¤–ã›ã¾ã›ã‚“。" + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã«åŠ ãˆã‚‰ã‚Œã¾ã›ã‚“。" + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "エラー" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "è¦å‘Š" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "コミットã™ã‚‹å‰ã«ã€ä»¥ä¸Šã®ã‚¨ãƒ©ãƒ¼ã‚’ä¿®æ£ã—ã¦ä¸‹ã•ã„" + +#: lib/index.tcl:241 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "コミットã‹ã‚‰ '%s' ã‚’é™ã‚ã™" + +#: lib/index.tcl:285 +#, tcl-format +msgid "Adding %s" +msgstr "コミット㫠%s ã‚’åŠ ãˆã¦ã„ã¾ã™" + +#: lib/index.tcl:340 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "ファイル %s ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "ã“れら %i 個ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "変更を元ã«æˆ»ã™ã¨ã‚³ãƒŸãƒƒãƒˆäºˆå®šã—ã¦ã„ãªã„変更ã¯å…¨ã¦å¤±ã‚ã‚Œã¾ã™ã€‚" + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "何もã—ãªã„" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"訂æ£ä¸ã«ã¯ãƒžãƒ¼ã‚¸ã§ãã¾ã›ã‚“。\n" +"\n" +"訂æ£å‡¦ç†ã‚’完了ã™ã‚‹ã¾ã§ã¯æ–°ãŸã«ãƒžãƒ¼ã‚¸ã‚’開始ã§ãã¾ã›ã‚“。\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" +"\n" +"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚マー" +"ジを開始ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"è¡çªã®ã‚ã£ãŸãƒžãƒ¼ã‚¸ã®é€”ä¸ã§ã™ã€‚\n" +"\n" +"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸ä¸ã®è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚\n" +"\n" +"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®è¡çªã‚’解決ã—ã€ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã¦ã€ã‚³ãƒŸãƒƒãƒˆã™ã‚‹ã“ã¨ã§ãƒžãƒ¼ã‚¸ã‚’" +"完了ã—ã¾ã™ã€‚ãã†ã‚„ã£ã¦å§‹ã‚ã¦ã€æ–°ãŸãªãƒžãƒ¼ã‚¸ã‚’開始ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"変更ã®é€”ä¸ã§ã™ã€‚\n" +"\n" +"ファイル %s ã¯å¤‰æ›´ä¸ã§ã™ã€‚\n" +"\n" +"ç¾åœ¨ã®ã‚³ãƒŸãƒƒãƒˆã‚’完了ã—ã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã‚’開始ã—ã¦ä¸‹ã•ã„。ãã†ã™ã‚‹æ–¹ãŒãƒžãƒ¼ã‚¸ã«å¤±æ•—" +"ã—ãŸã¨ãã®å›žå¾©ãŒæ¥½ã§ã™ã€‚\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s ã® %s ブランãƒ" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "%s 㨠%s をマージã—ã¾ã™" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "マージãŒå®Œäº†ã—ã¾ã—ãŸ" + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "マージãŒå¤±æ•—ã—ã¾ã—ãŸã€‚è¡çªã®è§£æ±ºãŒå¿…è¦ã§ã™ã€‚" + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "%s ã«ãƒžãƒ¼ã‚¸" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "マージã™ã‚‹ãƒªãƒ“ジョン" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"訂æ£ä¸ã«ã¯ä¸æ¢ã§ãã¾ã›ã‚“。\n" +"\n" +"ã¾ãšä»Šã®ã‚³ãƒŸãƒƒãƒˆè¨‚æ£ã‚’完了ã•ã›ã¦ä¸‹ã•ã„。\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"マージをä¸æ–ã—ã¾ã™ã‹ï¼Ÿ\n" +"\n" +"ç¾åœ¨ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ–ã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"\n" +"マージをä¸æ–ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"変更点をリセットã—ã¾ã™ã‹ï¼Ÿ\n" +"\n" +"変更点をリセットã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"\n" +"リセットã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "ä¸æ–ã—ã¦ã„ã¾ã™" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "ä¸æ–ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "ä¸æ–完了。" + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "既定値ã«æˆ»ã™" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "ä¿å˜" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "%s リãƒã‚¸ãƒˆãƒª" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "大域(全ã¦ã®ãƒªãƒã‚¸ãƒˆãƒªï¼‰" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "ユーザå" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "é›»åメールアドレス" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "マージコミットã®è¦ç´„" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "マージã®å†—長度" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "マージ後㫠diffstat を表示" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "ãƒ•ã‚¡ã‚¤ãƒ«å¤‰æ›´æ™‚åˆ»ã‚’ä¿¡é ¼ã™ã‚‹" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "フェッãƒä¸ã«ãƒˆãƒ©ãƒƒã‚ングブランãƒã‚’刈る" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "トラッã‚ングブランãƒã‚’åˆã‚ã›ã‚‹" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "diff ã®æ–‡è„ˆè¡Œæ•°" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "æ–°ã—ã„ブランãƒåã®ãƒ†ãƒ³ãƒ—レート" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "フォントを変更" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "%s ã‚’é¸æŠž" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "ãƒã‚¤ãƒ³ãƒˆ" + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "è¨å®š" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "完全ã«ã‚ªãƒ—ションをä¿å˜ã§ãã¾ã›ã‚“:" + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "ã‹ã‚‰åˆˆè¾¼ã‚€â€¦" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "å–å¾—å…ƒ" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "プッシュ先" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "リモート・ブランãƒã‚’削除" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "å…ƒã®ãƒªãƒã‚¸ãƒˆãƒª" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "リモート:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "ä»»æ„ã® URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "ブランãƒ" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "æ¡ä»¶ä»˜ã§å‰Šé™¤" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "マージ先:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "ç„¡æ¡ä»¶ï¼ˆãƒžãƒ¼ã‚¸æ¤œæŸ»ã‚’ã—ãªã„)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "'マージ先' ã«ã¯ãƒ–ランãƒãŒå¿…è¦ã§ã™ã€‚" + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"å¿…è¦ãªã‚³ãƒŸãƒƒãƒˆãŒä¸è¶³ã—ã¦ã„ã‚‹ãŸã‚ã«ã€ãƒžãƒ¼ã‚¸æ¤œæŸ»ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ã¾ãš %s ã‹ã‚‰" +"フェッãƒã—ã¦ä¸‹ã•ã„。" + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "削除ã™ã‚‹ãƒ–ランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"削除ã—ãŸãƒ–ランãƒã‚’回復ã™ã‚‹ã®ã¯å›°é›£ã§ã™ã€‚\n" +"\n" +"é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "%s ã‹ã‚‰ãƒ–ランãƒã‚’削除ã—ã¦ã„ã¾ã™ã€‚" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "リãƒã‚¸ãƒˆãƒªãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“。" + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "%s をスã‚ャンã—ã¦ã„ã¾ã™â€¦" + +#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 +msgid "Cannot write script:" +msgstr "スクリプトãŒæ›¸ã‘ã¾ã›ã‚“:" + +#: lib/shortcut.tcl:149 +msgid "Cannot write icon:" +msgstr "アイコンãŒæ›¸ã‘ã¾ã›ã‚“:" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%1$s ... %4$*i %6$s ä¸ã® %2$*i (%7$3i%%)" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "%s ã‚’å–å¾—" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "%s ã‹ã‚‰æ–°ã—ã„変更をフェッãƒã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "é 隔刈込 %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "%s ã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸãƒˆãƒ©ãƒƒã‚ング・ブランãƒã‚’刈ã£ã¦ã„ã¾ã™" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "%s をプッシュ" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "%s ã¸å¤‰æ›´ã‚’プッシュã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "%3$s 㸠%1$s %2$s をプッシュã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "ブランãƒã‚’プッシュ" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "å…ƒã®ãƒ–ランãƒ" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "é€ã‚Šå…ˆãƒªãƒã‚¸ãƒˆãƒª" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "通信オプション" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "æ—¢å˜ãƒ–ランãƒã‚’上書ã(å¤‰æ›´ã‚’ç ´æ£„ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Thin Pack を使ã†ï¼ˆé…ã„ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯æŽ¥ç¶šï¼‰" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "ã‚¿ã‚°ã‚’å«ã‚ã‚‹" + diff --git a/git-gui/po/po2msg.sh b/git-gui/po/po2msg.sh new file mode 100644 index 0000000000..c63248e375 --- /dev/null +++ b/git-gui/po/po2msg.sh @@ -0,0 +1,133 @@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec tclsh "$0" -- "$@" + +# This is a really stupid program, which serves as an alternative to +# msgfmt. It _only_ translates to Tcl mode, does _not_ validate the +# input, and does _not_ output any statistics. + +proc u2a {s} { + set res "" + foreach i [split $s ""] { + scan $i %c c + if {$c<128} { + # escape '[', '\' and ']' + if {$c == 0x5b || $c == 0x5d} { + append res "\\" + } + append res $i + } else { + append res \\u[format %04.4x $c] + } + } + return $res +} + +set output_directory "." +set lang "dummy" +set files [list] +set show_statistics 0 + +# parse options +for {set i 0} {$i < $argc} {incr i} { + set arg [lindex $argv $i] + if {$arg == "--statistics"} { + incr show_statistics + continue + } + if {$arg == "--tcl"} { + # we know + continue + } + if {$arg == "-l"} { + incr i + set lang [lindex $argv $i] + continue + } + if {$arg == "-d"} { + incr i + set tmp [lindex $argv $i] + regsub "\[^/\]$" $tmp "&/" output_directory + continue + } + lappend files $arg +} + +proc flush_msg {} { + global msgid msgstr mode lang out fuzzy + global translated_count fuzzy_count not_translated_count + + if {![info exists msgid] || $mode == ""} { + return + } + set mode "" + if {$fuzzy == 1} { + incr fuzzy_count + set fuzzy 0 + return + } + + if {$msgid == ""} { + set prefix "set ::msgcat::header" + } else { + if {$msgstr == ""} { + incr not_translated_count + return + } + set prefix "::msgcat::mcset $lang \"[u2a $msgid]\"" + incr translated_count + } + + puts $out "$prefix \"[u2a $msgstr]\"" +} + +set fuzzy 0 +set translated_count 0 +set fuzzy_count 0 +set not_translated_count 0 +foreach file $files { + regsub "^.*/\(\[^/\]*\)\.po$" $file "$output_directory\\1.msg" outfile + set in [open $file "r"] + fconfigure $in -encoding utf-8 + set out [open $outfile "w"] + + set mode "" + while {[gets $in line] >= 0} { + if {[regexp "^#" $line]} { + if {[regexp ", fuzzy" $line]} { + set fuzzy 1 + } else { + flush_msg + } + continue + } elseif {[regexp "^msgid \"(.*)\"$" $line dummy match]} { + flush_msg + set msgid $match + set mode "msgid" + } elseif {[regexp "^msgstr \"(.*)\"$" $line dummy match]} { + set msgstr $match + set mode "msgstr" + } elseif {$line == ""} { + flush_msg + } elseif {[regexp "^\"(.*)\"$" $line dummy match]} { + if {$mode == "msgid"} { + append msgid $match + } elseif {$mode == "msgstr"} { + append msgstr $match + } else { + puts stderr "I do not know what to do: $match" + } + } else { + puts stderr "Cannot handle $line" + } + } + flush_msg + close $in + close $out +} + +if {$show_statistics} { + puts [concat "$translated_count translated messages, " \ + "$fuzzy_count fuzzy ones, " \ + "$not_translated_count untranslated ones."] +} diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po new file mode 100644 index 0000000000..6727a832ea --- /dev/null +++ b/git-gui/po/ru.po @@ -0,0 +1,1893 @@ +# Translation of git-gui to russian +# Copyright (C) 2007 Shawn Pearce +# This file is distributed under the same license as the git-gui package. +# Irina Riesen <irina.riesen@gmail.com>, 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-31 21:23+0100\n" +"PO-Revision-Date: 2007-10-22 22:30-0200\n" +"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n" +"Language-Team: Russian Translation <git@vger.kernel.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:597 git-gui.sh:611 git-gui.sh:624 git-gui.sh:707 +#: git-gui.sh:726 +msgid "git-gui: fatal error" +msgstr "git-gui: критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°" + +#: git-gui.sh:558 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Ð’ %s уÑтановлен неверный шрифт:" + +#: git-gui.sh:583 +msgid "Main Font" +msgstr "Шрифт интерфейÑа" + +#: git-gui.sh:584 +msgid "Diff/Console Font" +msgstr "Шрифт конÑоли и изменений (diff)" + +#: git-gui.sh:598 +msgid "Cannot find git in PATH." +msgstr "git не найден в PATH." + +#: git-gui.sh:625 +msgid "Cannot parse Git version string:" +msgstr "Ðевозможно раÑпознать Ñтроку верÑии Git: " + +#: git-gui.sh:643 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Ðевозможно определить верÑию Git\n" +"%s указывает на верÑию '%s'.\n" +"\n" +"Ð´Ð»Ñ %s требуетÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Git, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1.5.0\n" +"\n" +"ПринÑÑ‚ÑŒ '%s' как верÑию 1.5.0?\n" + +#: git-gui.sh:881 +msgid "Git directory not found:" +msgstr "Каталог Git не найден:" + +#: git-gui.sh:888 +msgid "Cannot move to top of working directory:" +msgstr "Ðевозможно перейти к корню рабочего каталога репозиториÑ: " + +#: git-gui.sh:895 +msgid "Cannot use funny .git directory:" +msgstr "Каталог.git иÑпорчен: " + +#: git-gui.sh:900 +msgid "No working directory" +msgstr "ОтÑутÑтвует рабочий каталог" + +#: git-gui.sh:1047 +msgid "Refreshing file status..." +msgstr "Обновление информации о ÑоÑтоÑнии файлов..." + +#: git-gui.sh:1112 +msgid "Scanning for modified files ..." +msgstr "ПоиÑк измененных файлов..." + +#: git-gui.sh:1287 lib/browser.tcl:245 +msgid "Ready." +msgstr "Готово." + +#: git-gui.sh:1553 +msgid "Unmodified" +msgstr "Ðе изменено" + +#: git-gui.sh:1555 +msgid "Modified, not staged" +msgstr "Изменено, не подготовлено" + +#: git-gui.sh:1556 git-gui.sh:1561 +msgid "Staged for commit" +msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:1557 git-gui.sh:1562 +msgid "Portions staged for commit" +msgstr "ЧаÑти, подготовленные Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:1558 git-gui.sh:1563 +msgid "Staged for commit, missing" +msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ, отÑутÑтвует" + +#: git-gui.sh:1560 +msgid "Untracked, not staged" +msgstr "Ðе отÑлеживаетÑÑ, не подготовлено" + +#: git-gui.sh:1565 +msgid "Missing" +msgstr "ОтÑутÑтвует" + +#: git-gui.sh:1566 +msgid "Staged for removal" +msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ" + +#: git-gui.sh:1567 +msgid "Staged for removal, still present" +msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ, еще не удалено" + +#: git-gui.sh:1569 git-gui.sh:1570 git-gui.sh:1571 git-gui.sh:1572 +msgid "Requires merge resolution" +msgstr "ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта при объединении" + +#: git-gui.sh:1607 +msgid "Starting gitk... please wait..." +msgstr "ЗапуÑкаетÑÑ gitk... пожалуйÑта, ждите..." + +#: git-gui.sh:1616 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"Ðе удалоÑÑŒ запуÑтить gitk:\n" +"\n" +"%s не ÑущеÑтвует" + +#: git-gui.sh:1816 lib/choose_repository.tcl:35 +msgid "Repository" +msgstr "Репозиторий" + +#: git-gui.sh:1817 +msgid "Edit" +msgstr "Редактировать" + +#: git-gui.sh:1819 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "Ветвь" + +#: git-gui.sh:1822 lib/choose_rev.tcl:547 +msgid "Commit@@noun" +msgstr "СоÑтоÑние" + +#: git-gui.sh:1825 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "Объединить" + +#: git-gui.sh:1826 lib/choose_rev.tcl:556 +msgid "Remote" +msgstr "Внешние репозитории" + +#: git-gui.sh:1835 +msgid "Browse Current Branch's Files" +msgstr "ПроÑмотреть файлы текущей ветви" + +#: git-gui.sh:1839 +msgid "Browse Branch Files..." +msgstr "Показать файлы ветви..." + +#: git-gui.sh:1844 +msgid "Visualize Current Branch's History" +msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ ветви наглÑдно" + +#: git-gui.sh:1848 +msgid "Visualize All Branch History" +msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ñех ветвей наглÑдно" + +#: git-gui.sh:1855 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Показать файлы ветви %s" + +#: git-gui.sh:1857 +#, tcl-format +msgid "Visualize %s's History" +msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²ÐµÑ‚Ð²Ð¸ %s наглÑдно" + +#: git-gui.sh:1862 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "СтатиÑтика базы данных" + +#: git-gui.sh:1865 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Сжать базу данных" + +#: git-gui.sh:1868 +msgid "Verify Database" +msgstr "Проверить базу данных" + +#: git-gui.sh:1875 git-gui.sh:1879 git-gui.sh:1883 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +msgid "Create Desktop Icon" +msgstr "Создать Ñрлык на рабочем Ñтоле" + +#: git-gui.sh:1888 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184 +msgid "Quit" +msgstr "Выход" + +#: git-gui.sh:1895 +msgid "Undo" +msgstr "Отменить" + +#: git-gui.sh:1898 +msgid "Redo" +msgstr "Повторить" + +#: git-gui.sh:1902 git-gui.sh:2395 +msgid "Cut" +msgstr "Вырезать" + +#: git-gui.sh:1905 git-gui.sh:2398 git-gui.sh:2469 git-gui.sh:2541 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "Копировать" + +#: git-gui.sh:1908 git-gui.sh:2401 +msgid "Paste" +msgstr "Ð’Ñтавить" + +#: git-gui.sh:1911 git-gui.sh:2404 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Удалить" + +#: git-gui.sh:1915 git-gui.sh:2408 git-gui.sh:2545 lib/console.tcl:69 +msgid "Select All" +msgstr "Выделить вÑе" + +#: git-gui.sh:1924 +msgid "Create..." +msgstr "Создать..." + +#: git-gui.sh:1930 +msgid "Checkout..." +msgstr "Перейти..." + +#: git-gui.sh:1936 +msgid "Rename..." +msgstr "Переименовать..." + +#: git-gui.sh:1941 git-gui.sh:2040 +msgid "Delete..." +msgstr "Удалить..." + +#: git-gui.sh:1946 +msgid "Reset..." +msgstr "СброÑить..." + +#: git-gui.sh:1958 git-gui.sh:2342 +msgid "New Commit" +msgstr "Ðовое ÑоÑтоÑние" + +#: git-gui.sh:1966 git-gui.sh:2349 +msgid "Amend Last Commit" +msgstr "ИÑправить поÑледнее ÑоÑтоÑние" + +#: git-gui.sh:1975 git-gui.sh:2309 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Перечитать" + +#: git-gui.sh:1981 +msgid "Stage To Commit" +msgstr "Подготовить Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:1986 +msgid "Stage Changed Files To Commit" +msgstr "Подготовить измененные файлы Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:1992 +msgid "Unstage From Commit" +msgstr "Убрать из подготовленного" + +#: git-gui.sh:1997 lib/index.tcl:393 +msgid "Revert Changes" +msgstr "Отменить изменениÑ" + +#: git-gui.sh:2004 git-gui.sh:2321 git-gui.sh:2419 +msgid "Sign Off" +msgstr "ПодпиÑать" + +#: git-gui.sh:2008 git-gui.sh:2325 +msgid "Commit@@verb" +msgstr "Сохранить" + +#: git-gui.sh:2019 +msgid "Local Merge..." +msgstr "Локальное объединение..." + +#: git-gui.sh:2024 +msgid "Abort Merge..." +msgstr "Прервать объединение..." + +#: git-gui.sh:2036 +msgid "Push..." +msgstr "Отправить..." + +#: git-gui.sh:2047 lib/choose_repository.tcl:40 +msgid "Apple" +msgstr "" + +#: git-gui.sh:2050 git-gui.sh:2072 lib/about.tcl:13 +#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49 +#, tcl-format +msgid "About %s" +msgstr "О %s" + +#: git-gui.sh:2054 +msgid "Preferences..." +msgstr "ÐаÑтройки..." + +#: git-gui.sh:2062 git-gui.sh:2587 +msgid "Options..." +msgstr "ÐаÑтройки..." + +#: git-gui.sh:2068 lib/choose_repository.tcl:46 +msgid "Help" +msgstr "Помощь" + +#: git-gui.sh:2109 +msgid "Online Documentation" +msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð² интернете" + +#: git-gui.sh:2193 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %s: нет такого файла или каталога" + +#: git-gui.sh:2226 +msgid "Current Branch:" +msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:" + +#: git-gui.sh:2247 +msgid "Staged Changes (Will Commit)" +msgstr "Подготовлено (будет Ñохранено)" + +#: git-gui.sh:2266 +msgid "Unstaged Changes" +msgstr "Изменено (не будет Ñохранено)" + +#: git-gui.sh:2315 +msgid "Stage Changed" +msgstr "Подготовить вÑе" + +#: git-gui.sh:2331 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Отправить" + +#: git-gui.sh:2361 +msgid "Initial Commit Message:" +msgstr "Комментарий к первому ÑоÑтоÑнию:" + +#: git-gui.sh:2362 +msgid "Amended Commit Message:" +msgstr "Комментарий к иÑправленному ÑоÑтоÑнию:" + +#: git-gui.sh:2363 +msgid "Amended Initial Commit Message:" +msgstr "Комментарий к иÑправленному первоначальному ÑоÑтоÑнию:" + +#: git-gui.sh:2364 +msgid "Amended Merge Commit Message:" +msgstr "Комментарий к иÑправленному объединению:" + +#: git-gui.sh:2365 +msgid "Merge Commit Message:" +msgstr "Комментарий к объединению:" + +#: git-gui.sh:2366 +msgid "Commit Message:" +msgstr "Комментарий к ÑоÑтоÑнию:" + +#: git-gui.sh:2411 git-gui.sh:2549 lib/console.tcl:71 +msgid "Copy All" +msgstr "Копировать вÑе" + +#: git-gui.sh:2435 lib/blame.tcl:104 +msgid "File:" +msgstr "Файл:" + +#: git-gui.sh:2537 +msgid "Refresh" +msgstr "Обновить" + +#: git-gui.sh:2558 +msgid "Apply/Reverse Hunk" +msgstr "Применить/Убрать изменение" + +#: git-gui.sh:2564 +msgid "Decrease Font Size" +msgstr "Уменьшить размер шрифта" + +#: git-gui.sh:2568 +msgid "Increase Font Size" +msgstr "Увеличить размер шрифта" + +#: git-gui.sh:2573 +msgid "Show Less Context" +msgstr "Меньше контекÑта" + +#: git-gui.sh:2580 +msgid "Show More Context" +msgstr "Больше контекÑта" + +#: git-gui.sh:2594 +msgid "Unstage Hunk From Commit" +msgstr "Ðе ÑохранÑÑ‚ÑŒ чаÑÑ‚ÑŒ" + +#: git-gui.sh:2596 +msgid "Stage Hunk For Commit" +msgstr "Подготовить чаÑÑ‚ÑŒ Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:2615 +msgid "Initializing..." +msgstr "ИнициализациÑ..." + +#: git-gui.sh:2706 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"Возможны ошибки в переменных окружениÑ.\n" +"\n" +"Переменные окружениÑ, которые возможно\n" +"будут проигнорированы командами Git,\n" +"запущенными из %s\n" +"\n" + +#: git-gui.sh:2736 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"Ðто извеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ Tcl,\n" +"раÑпроÑтранÑемым Cygwin." + +#: git-gui.sh:2741 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"ВмеÑто иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s можно\n" +"Ñохранить Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ user.name и\n" +"user.email в Вашем перÑональном\n" +"файле ~/.gitconfig.\n" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - графичеÑкий пользовательÑкий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ðº Git." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "ПроÑмотр файла" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Сохраненное ÑоÑтоÑние:" + +#: lib/blame.tcl:249 +msgid "Copy Commit" +msgstr "Скопировать SHA-1" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "Чтение %s..." + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "Загрузка аннотации копирований/переименований..." + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "Ñтрок прокомментировано" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "Загрузка аннотаций первоначального Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°..." + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð°." + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "Загрузка аннотации..." + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "Ðвтор:" + +#: lib/blame.tcl:791 +msgid "Committer:" +msgstr "Сохранил:" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "ИÑходный файл:" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "ИÑточник:" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "Файл:" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "Скопировано/перемещено в:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Перейти на ветвь" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Перейти" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Отменить" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "ВерÑиÑ" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +msgid "Options" +msgstr "ÐаÑтройки" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Получить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð· внешней ветви" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "ОтÑоединить от локальной ветви" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Создание ветви" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Создать новую ветвь" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375 +msgid "Create" +msgstr "Создать" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Ðазвание ветви" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Ðазвание:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "ВзÑÑ‚ÑŒ из имен ветвей ÑлежениÑ" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Обновить имеющуюÑÑ Ð²ÐµÑ‚Ð²ÑŒ:" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "Ðет" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Только Fast Forward" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "СброÑ" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "ПоÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñделать текущей" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Укажите ветвь ÑлежениÑ." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "Ветвь ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ %s не ÑвлÑетÑÑ Ð²ÐµÑ‚Ð²ÑŒÑŽ во внешнем репозитории." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Укажите название ветви." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "ÐедопуÑтимое название ветви '%s'." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Удаление ветви" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Удалить локальную ветвь" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Локальные ветви" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Удалить только в Ñлучае, еÑли было объединение Ñ" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку на объединение)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "Следующие ветви объединены Ñ %s не полноÑтью:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"ВоÑÑтанавливать удаленные ветви Ñложно. \n" +"\n" +" Удалить выбранные ветви?" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Ðе удалоÑÑŒ удалить ветви:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Переименование ветви" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Переименовать" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Ветвь:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Ðовое название:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Укажите ветвь Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ." + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "Ветвь '%s' уже ÑущеÑтвует." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Ðе удалоÑÑŒ переименовать '%s'. " + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "ЗапуÑк..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "ПроÑмотр ÑпиÑка файлов" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "Загрузка %s..." + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "[Ðа уровень выше]" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +msgid "Browse Branch Files" +msgstr "Показать файлы ветви" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:391 +#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492 +#: lib/choose_repository.tcl:989 +msgid "Browse" +msgstr "Показать" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Получение %s из %s " + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: невозможно разрешить %s" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "Закрыть" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Ветвь '%s' не ÑущеÑтвует " + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"Ветвь '%s' уже ÑущеÑтвует.\n" +"\n" +"Она не может быть прокручена(fast-forward) к %s.\n" +"ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ '%s' не поддерживаетÑÑ." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Ðе удалоÑÑŒ обновить '%s'." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ заблокирована другим процеÑÑом." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n" +"\n" +"С момента поÑледней проверки репозиторий был изменен другой программой Git. " +"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь.\n" +"\n" +"Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Обновление рабочего каталога из '%s'..." + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "Прерван переход на '%s' (требуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов)" + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Ветвь '%s' оÑтаетÑÑ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Ð’Ñ‹ находитеÑÑŒ не в локальной ветви.\n" +"\n" +"ЕÑли вы хотите Ñнова вернутьÑÑ Ðº какой-нибудь ветви, Ñоздайте ее ÑейчаÑ, " +"Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 'Текущего отÑоединенного ÑоÑтоÑниÑ'." + +#: lib/checkout_op.tcl:446 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Ветвь '%s' Ñделана текущей." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "Ð¡Ð±Ñ€Ð¾Ñ '%s' в '%s' приведет к потере Ñледующих Ñохраненных ÑоÑтоÑний: " + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "ВоÑÑтановить потерÑнные Ñохраненные ÑоÑтоÑÐ½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ Ñложно." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "СброÑить '%s'?" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "ÐаглÑдно" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Ðе удалоÑÑŒ уÑтановить текущую ветвь.\n" +"\n" +"Ваш рабочий каталог обновлен только чаÑтично. Были обновлены вÑе файлы кроме " +"Ñлужебных файлов Git. \n" +"\n" +"Ðтого не должно было произойти. %s завершаетÑÑ." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Выбрать" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Шрифт" + +#: lib/choose_font.tcl:73 +msgid "Font Size" +msgstr "Размер шрифта" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "Пример текÑта" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Ðто пример текÑта.\n" +"ЕÑли Вам нравитÑÑ Ñтот текÑÑ‚, Ñто может быть Ваш шрифт." + +#: lib/choose_repository.tcl:27 +msgid "Git Gui" +msgstr "" + +#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380 +msgid "Create New Repository" +msgstr "Создать новый репозиторий" + +#: lib/choose_repository.tcl:86 +msgid "New..." +msgstr "Ðовый..." + +#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468 +msgid "Clone Existing Repository" +msgstr "Склонировать ÑущеÑтвующий репозиторий" + +#: lib/choose_repository.tcl:99 +msgid "Clone..." +msgstr "Склонировать..." + +#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978 +msgid "Open Existing Repository" +msgstr "Выбрать ÑущеÑтвующий репозиторий" + +#: lib/choose_repository.tcl:112 +msgid "Open..." +msgstr "Открыть..." + +#: lib/choose_repository.tcl:125 +msgid "Recent Repositories" +msgstr "Ðедавние репозитории" + +#: lib/choose_repository.tcl:131 +msgid "Open Recent Repository:" +msgstr "Открыть поÑледний репозиторий" + +#: lib/choose_repository.tcl:294 +#, tcl-format +msgid "Location %s already exists." +msgstr "Путь '%s' уже ÑущеÑтвует." + +#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307 +#: lib/choose_repository.tcl:314 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Ðе удалоÑÑŒ Ñоздать репозиторий %s:" + +#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486 +msgid "Directory:" +msgstr "Каталог:" + +#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544 +#: lib/choose_repository.tcl:1013 +msgid "Git Repository" +msgstr "Репозиторий" + +#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437 +#, tcl-format +msgid "Directory %s already exists." +msgstr "Каталог '%s' уже ÑущеÑтвует." + +#: lib/choose_repository.tcl:442 +#, tcl-format +msgid "File %s already exists." +msgstr "Файл '%s' уже ÑущеÑтвует." + +#: lib/choose_repository.tcl:463 +msgid "Clone" +msgstr "Склонировать" + +#: lib/choose_repository.tcl:476 +msgid "URL:" +msgstr "СÑылка:" + +#: lib/choose_repository.tcl:496 +msgid "Clone Type:" +msgstr "Тип клона:" + +#: lib/choose_repository.tcl:502 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Стандартный (БыÑтрый, полуизбыточный, \"жеÑткие\" ÑÑылки)" + +#: lib/choose_repository.tcl:508 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "ÐŸÐ¾Ð»Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ (Медленный, Ñоздает резервную копию)" + +#: lib/choose_repository.tcl:514 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Общий (Самый быÑтрый, не рекомендуетÑÑ, без резервной копии)" + +#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 +#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808 +#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Каталог не ÑвлÑетÑÑ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸ÐµÐ¼: %s" + +#: lib/choose_repository.tcl:586 +msgid "Standard only available for local repository." +msgstr "Стандартный клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ." + +#: lib/choose_repository.tcl:590 +msgid "Shared only available for local repository." +msgstr "Общий клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ." + +#: lib/choose_repository.tcl:617 +msgid "Failed to configure origin" +msgstr "Ðе могу Ñконфигурировать иÑходный репозиторий." + +#: lib/choose_repository.tcl:629 +msgid "Counting objects" +msgstr "Считаю объекты" + +#: lib/choose_repository.tcl:630 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:654 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "Ðе могу Ñкопировать objects/info/alternates: %s" + +#: lib/choose_repository.tcl:690 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Ðечего клонировать Ñ %s." + +#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906 +#: lib/choose_repository.tcl:918 +msgid "The 'master' branch has not been initialized." +msgstr "Ðе инициализирована ветвь 'master'." + +#: lib/choose_repository.tcl:705 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "\"ЖеÑткие ÑÑылки\" не доÑтупны. Буду иÑпользовать копирование." + +#: lib/choose_repository.tcl:717 +#, tcl-format +msgid "Cloning from %s" +msgstr "Клонирование %s" + +#: lib/choose_repository.tcl:748 +msgid "Copying objects" +msgstr "Копирование objects" + +#: lib/choose_repository.tcl:749 +msgid "KiB" +msgstr "КБ" + +#: lib/choose_repository.tcl:773 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Ðе могу Ñкопировать объект: %s" + +#: lib/choose_repository.tcl:783 +msgid "Linking objects" +msgstr "Создание ÑÑылок на objects" + +#: lib/choose_repository.tcl:784 +msgid "objects" +msgstr "объекты" + +#: lib/choose_repository.tcl:792 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Ðе могу \"жеÑтко ÑвÑзать\" объект: %s" + +#: lib/choose_repository.tcl:847 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" +"Ðе могу получить ветви и объекты. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." + +#: lib/choose_repository.tcl:858 +msgid "Cannot fetch tags. See console output for details." +msgstr "Ðе могу получить метки. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." + +#: lib/choose_repository.tcl:882 +msgid "Cannot determine HEAD. See console output for details." +msgstr "Ðе могу определить HEAD. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." + +#: lib/choose_repository.tcl:891 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "Ðе могу очиÑтить %s" + +#: lib/choose_repository.tcl:897 +msgid "Clone failed." +msgstr "Клонирование не удалоÑÑŒ." + +#: lib/choose_repository.tcl:904 +msgid "No default branch obtained." +msgstr "Ðе было получено ветви по умолчанию." + +#: lib/choose_repository.tcl:915 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "Ðе могу раÑпознать %s как ÑоÑтоÑние." + +#: lib/choose_repository.tcl:927 +msgid "Creating working directory" +msgstr "Создаю рабочий каталог" + +#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127 +#: lib/index.tcl:193 +msgid "files" +msgstr "файлов" + +#: lib/choose_repository.tcl:957 +msgid "Initial file checkout failed." +msgstr "Ðе удалоÑÑŒ получить начальное ÑоÑтоÑние файлов репозиториÑ." + +#: lib/choose_repository.tcl:973 +msgid "Open" +msgstr "Открыть" + +#: lib/choose_repository.tcl:983 +msgid "Repository:" +msgstr "Репозиторий:" + +#: lib/choose_repository.tcl:1033 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Ðе удалоÑÑŒ открыть репозиторий %s:" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "Текущее отÑоединенное ÑоÑтоÑние" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Выражение Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñии:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Ветвь ÑлежениÑ" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "Таг" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "ВерÑÐ¸Ñ Ð½Ðµ указана." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "ПуÑтое выражение Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñии." + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "Обновлено" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "СÑылка" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"ОтÑутÑтвует ÑоÑтоÑние Ð´Ð»Ñ Ð¸ÑправлениÑ.\n" +"\n" +"Ð’Ñ‹ Ñоздаете первое ÑоÑтоÑние в репозитории, здеÑÑŒ еще нечего иÑправлÑÑ‚ÑŒ.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Ðевозможно иÑправить ÑоÑтоÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ.\n" +"\n" +"Текущее объединение не завершено. Ðевозможно иÑправить предыдущее " +"Ñохраненное ÑоÑтоÑние не Ð¿Ñ€ÐµÑ€Ñ‹Ð²Ð°Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐµ объединение.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Ошибка при загрузке данных Ð´Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Ðевозможно получить информацию об авторÑтве:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Ðеверный GIT_COMMITTER_IDENT:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n" +"\n" +"С момента поÑледней проверки репозиторий был изменен другой программой Git. " +"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь. \n" +"\n" +"Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"ÐÐµÐ»ÑŒÐ·Ñ Ñохранить необъединенные файлы.\n" +"\n" +"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ. Разрешите конфликт и добавьте к " +"подготовленным файлам перед Ñохранением.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Обнаружено неизвеÑтное ÑоÑтоÑние файла %s.\n" +"\n" +"Файл %s не может быть Ñохранен данной программой.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n" +"\n" +"Подготовьте Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один файл до ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Ðапишите комментарий к Ñохраненному ÑоÑтоÑнию.\n" +"\n" +"РекомендуетÑÑ Ñледующий формат комментариÑ:\n" +"\n" +"- Ð¿ÐµÑ€Ð²Ð°Ñ Ñтрока: краткое опиÑание Ñделанных изменений.\n" +"- Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ñтрока пуÑтаÑ\n" +"- оÑтавшиеÑÑ Ñтроки: опишите, что дают ваши изменениÑ.\n" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "Программа write-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "СоÑтоÑние %s выглÑдит поврежденным" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n" +"\n" +"Ðи один файл не был изменен и не было объединениÑ.\n" +"\n" +"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки запуÑтитÑÑ Ð¿ÐµÑ€ÐµÑ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ðµ репозиториÑ.\n" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "ОтуÑтвуют Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ." + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "предупреждение: Tcl не поддерживает кодировку '%s'." + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "Программа commit-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "Программа update-ref завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Создано ÑоÑтоÑние %s: %s " + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "Ð’ процеÑÑе... пожалуйÑта, ждите..." + +#: lib/console.tcl:183 +msgid "Success" +msgstr "ПроцеÑÑ ÑƒÑпешно завершен" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "Ошибка: не удалоÑÑŒ выполнить команду" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "КоличеÑтво неÑвÑзанных объектов" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Объем диÑкового проÑтранÑтва, занÑтый неÑвÑзанными объектами" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "КоличеÑтво упакованных объектов" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "КоличеÑтво pack-файлов" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Объем диÑкового проÑтранÑтва, занÑтый упакованными объектами" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "ÐеÑвÑзанные объекты, которые можно удалить" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "МуÑор" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Сжатие базы объектов" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Проверка базы объектов при помощи fsck" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"Ðтот репозиторий ÑÐµÐ¹Ñ‡Ð°Ñ Ñодержит примерно %i Ñвободных объектов\n" +"\n" +"Ð”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ производительноÑти рекомендуетÑÑ Ñжать базу данных, когда еÑÑ‚ÑŒ " +"более %i неÑвÑзанных объектов.\n" +"\n" +"Сжать базу данных ÑейчаÑ?" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° в репозитории: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Изменений не обнаружено.\n" +"\n" +"в %s отутÑтвуют изменениÑ.\n" +"\n" +"Дата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° была обновлена другой программой, но Ñодержимое файла " +"оÑталоÑÑŒ прежним.\n" +"\n" +"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ запущено перечитывание репозиториÑ, чтобы найти подобные файлы." + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Загрузка изменений в %s..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "Ðе могу показать %s" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Ошибка загрузки файла:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Репозиторий Git (подпроект)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* Двоичный файл (Ñодержимое не показано)" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Ошибка загрузки diff:" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "Ðе удалоÑÑŒ иÑключить выбранную чаÑÑ‚ÑŒ." + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную чаÑÑ‚ÑŒ." + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "ошибка" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "предупреждение" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "Прежде чем Ñохранить, иÑправьте вышеуказанные ошибки." + +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "Ðе удалоÑÑŒ разблокировать индекÑ" + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "Ошибка индекÑа" + +#: lib/index.tcl:21 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "" +"Ðе удалоÑÑŒ обновить Ð¸Ð½Ð´ÐµÐºÑ Git. СоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚" +"перечитано автоматичеÑки." + +#: lib/index.tcl:27 +msgid "Continue" +msgstr "Продолжить" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "Разблокировать индекÑ" + +#: lib/index.tcl:282 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "Удаление %s из подготовленного" + +#: lib/index.tcl:326 +#, tcl-format +msgid "Adding %s" +msgstr "Добавление %s..." + +#: lib/index.tcl:381 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файле %s?" + +#: lib/index.tcl:383 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %i файле(-ах)?" + +#: lib/index.tcl:389 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Любые изменениÑ, не подготовленные к Ñохранению, будут потерÑны при данной " +"операции." + +#: lib/index.tcl:392 +msgid "Do Nothing" +msgstr "Ðичего не делать" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Ðевозможно выполнить объединение во Ð²Ñ€ÐµÐ¼Ñ Ð¸ÑправлениÑ.\n" +"\n" +"Завершите иÑправление данного ÑоÑтоÑÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ выполнением операции " +"объединениÑ.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n" +"\n" +"С момента поÑледней проверки репозиторий был изменен другой программой Git. " +"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь.\n" +"\n" +"Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Предыдущее объединение не завершено из-за конфликта.\n" +"\n" +"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ.\n" +"\n" +"Разрешите конфликт, подготовьте файл и Ñохраните. Только поÑле Ñтого можно " +"начать Ñледующее объединение.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ Ñохранены.\n" +"\n" +"Файл %s изменен.\n" +"\n" +"Подготовьте и Ñохраните Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ началом объединениÑ. Ð’ Ñлучае " +"необходимоÑти Ñто позволит прервать операцию объединениÑ.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s из %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "Объединение %s и %s" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "Объединение уÑпешно завершено." + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "Ðе удалоÑÑŒ завершить объединение. ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта." + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "Объединить Ñ %s" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "ВерÑÐ¸Ñ Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Ðевозможно прервать иÑправление.\n" +"\n" +"Завершите текущее иÑправление Ñохраненного ÑоÑтоÑниÑ.\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Прервать объединение?\n" +"\n" +"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n" +"\n" +"Продолжить?" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Прервать объединение?\n" +"\n" +"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n" +"\n" +"Продолжить?" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "Прерываю" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "Прервать не удалоÑÑŒ." + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "Прервано." + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "ВоÑÑтановить наÑтройки по умолчанию" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "Сохранить" + +#: lib/option.tcl:96 +#, tcl-format +msgid "%s Repository" +msgstr "Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ %s" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "Общие (Ð´Ð»Ñ Ð²Ñех репозиториев)" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "ÐÐ´ÐµÑ Ñлектронной почты" + +#: lib/option.tcl:106 +msgid "Summarize Merge Commits" +msgstr "Суммарный комментарий при объединении" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "Уровень детальноÑти Ñообщений при объединении" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "Показать отчет об изменениÑÑ… поÑле объединениÑ" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "ДоверÑÑ‚ÑŒ времени модификации файла" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "ЧиÑтка ветвей ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ получении изменений" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "Ð˜Ð¼Ñ Ð½Ð¾Ð²Ð¾Ð¹ ветви взÑÑ‚ÑŒ из имен ветвей ÑлежениÑ" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "ЧиÑло Ñтрок в контекÑте diff" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "Шаблон Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ новой ветви" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "Изменить шрифт" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "Выберите %s" + +# carbon copy +#: lib/option.tcl:186 +msgid "pt." +msgstr "" + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "ÐаÑтройки" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "Ðе удалоÑÑŒ полноÑтью Ñохранить наÑтройки:" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Удалить внешнюю ветвь" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Из репозиториÑ" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "внешний:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "по указанному URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Ветви" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Удалить только в Ñлучае, еÑли" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Объединено Ñ:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку объединений)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ 'Объединено Ñ' требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветвь." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"Следующие ветви объединены Ñ %s не полноÑтью:\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Один или неÑколько теÑтов на объединение не прошли, потому что Ð’Ñ‹ не " +"получили необходимые ÑоÑтоÑниÑ. ПопытайтеÑÑŒ получить их из %s." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Укажите одну или неÑколько ветвей Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"ВоÑÑтановить удаленные ветви Ñложно.\n" +"\n" +"Продолжить?" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Удаление ветвей из %s" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Ðе указан репозиторий." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "Перечитывание %s... " + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "ЧиÑтка" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Получение из" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Отправить" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "Ðевозможно запиÑать ÑÑылку:" + +#: lib/shortcut.tcl:136 +msgid "Cannot write icon:" +msgstr "Ðевозможно запиÑать значок:" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i из %*i %s (%3i%%)" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "получение %s" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Получение изменений из %s " + +# carbon copy +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "чиÑтка внешнего %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "ЧиÑтка ветвей ÑлежениÑ, удаленных из %s" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "отправить %s" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Отправка изменений в %s " + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Отправка %s %s в %s" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Отправить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² ветвÑÑ…" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "ИÑходные ветви" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Репозиторий назначениÑ" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "ÐаÑтройки отправки" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "Ðамеренно перепиÑать ÑущеÑтвующую ветвь (возможна Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "ИÑпользовать thin pack (Ð´Ð»Ñ Ð¼ÐµÐ´Ð»ÐµÐ½Ð½Ñ‹Ñ… Ñетевых подключений)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Передать таги" + +#~ msgid "Next >" +#~ msgstr "Дальше >" diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po new file mode 100644 index 0000000000..621c9479b2 --- /dev/null +++ b/git-gui/po/zh_cn.po @@ -0,0 +1,1769 @@ +# Translation of git-gui to Chinese +# Copyright (C) 2007 Shawn Pearce +# This file is distributed under the same license as the git-gui package. +# Xudong Guan <xudong.guan@gmail.com>, 2007. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"PO-Revision-Date: 2007-07-21 01:23-0700\n" +"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n" +"Language-Team: Chinese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "" + +#: git-gui.sh:595 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "" + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" + +#: git-gui.sh:853 +msgid "Git directory not found:" +msgstr "" + +#: git-gui.sh:860 +msgid "Cannot move to top of working directory:" +msgstr "" + +#: git-gui.sh:867 +msgid "Cannot use funny .git directory:" +msgstr "" + +#: git-gui.sh:872 +msgid "No working directory" +msgstr "" + +#: git-gui.sh:1019 +msgid "Refreshing file status..." +msgstr "" + +#: git-gui.sh:1084 +msgid "Scanning for modified files ..." +msgstr "" + +#: git-gui.sh:1259 lib/browser.tcl:245 +#, fuzzy +msgid "Ready." +msgstr "é‡åš" + +#: git-gui.sh:1525 +msgid "Unmodified" +msgstr "" + +#: git-gui.sh:1527 +msgid "Modified, not staged" +msgstr "" + +#: git-gui.sh:1528 git-gui.sh:1533 +#, fuzzy +msgid "Staged for commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:1529 git-gui.sh:1534 +#, fuzzy +msgid "Portions staged for commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:1530 git-gui.sh:1535 +msgid "Staged for commit, missing" +msgstr "" + +#: git-gui.sh:1532 +msgid "Untracked, not staged" +msgstr "" + +#: git-gui.sh:1537 +msgid "Missing" +msgstr "" + +#: git-gui.sh:1538 +msgid "Staged for removal" +msgstr "" + +#: git-gui.sh:1539 +msgid "Staged for removal, still present" +msgstr "" + +#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +msgid "Requires merge resolution" +msgstr "" + +#: git-gui.sh:1579 +msgid "Starting gitk... please wait..." +msgstr "" + +#: git-gui.sh:1588 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" + +#: git-gui.sh:1788 lib/choose_repository.tcl:32 +msgid "Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: git-gui.sh:1789 +msgid "Edit" +msgstr "编辑" + +#: git-gui.sh:1791 lib/choose_rev.tcl:560 +msgid "Branch" +msgstr "分支" + +#: git-gui.sh:1794 lib/choose_rev.tcl:547 +#, fuzzy +msgid "Commit@@noun" +msgstr "æ交" + +#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "åˆå¹¶" + +#: git-gui.sh:1798 lib/choose_rev.tcl:556 +#, fuzzy +msgid "Remote" +msgstr "改å..." + +#: git-gui.sh:1807 +msgid "Browse Current Branch's Files" +msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶" + +#: git-gui.sh:1811 +#, fuzzy +msgid "Browse Branch Files..." +msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶" + +#: git-gui.sh:1816 +msgid "Visualize Current Branch's History" +msgstr "调用gitk显示当å‰åˆ†æ”¯" + +#: git-gui.sh:1820 +msgid "Visualize All Branch History" +msgstr "调用gitk显示所有分支" + +#: git-gui.sh:1827 +#, fuzzy, tcl-format +msgid "Browse %s's Files" +msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶" + +#: git-gui.sh:1829 +#, fuzzy, tcl-format +msgid "Visualize %s's History" +msgstr "调用gitk显示所有分支" + +#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "æ•°æ®åº“统计数æ®" + +#: git-gui.sh:1837 lib/database.tcl:34 +msgid "Compress Database" +msgstr "压缩数æ®åº“" + +#: git-gui.sh:1840 +msgid "Verify Database" +msgstr "验è¯æ•°æ®åº“" + +#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 +#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +msgid "Create Desktop Icon" +msgstr "创建桌é¢å›¾æ ‡" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +msgid "Quit" +msgstr "退出" + +#: git-gui.sh:1867 +msgid "Undo" +msgstr "撤销" + +#: git-gui.sh:1870 +msgid "Redo" +msgstr "é‡åš" + +#: git-gui.sh:1874 git-gui.sh:2366 +msgid "Cut" +msgstr "剪切" + +#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: lib/console.tcl:67 +msgid "Copy" +msgstr "å¤åˆ¶" + +#: git-gui.sh:1880 git-gui.sh:2372 +msgid "Paste" +msgstr "粘贴" + +#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "åˆ é™¤" + +#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +msgid "Select All" +msgstr "全选" + +#: git-gui.sh:1896 +msgid "Create..." +msgstr "新建..." + +#: git-gui.sh:1902 +msgid "Checkout..." +msgstr "切æ¢..." + +#: git-gui.sh:1908 +msgid "Rename..." +msgstr "改å..." + +#: git-gui.sh:1913 git-gui.sh:2012 +msgid "Delete..." +msgstr "åˆ é™¤..." + +#: git-gui.sh:1918 +msgid "Reset..." +msgstr "é‡ç½®æ‰€æœ‰ä¿®åŠ¨..." + +#: git-gui.sh:1930 git-gui.sh:2313 +msgid "New Commit" +msgstr "æ–°æ交" + +#: git-gui.sh:1938 git-gui.sh:2320 +msgid "Amend Last Commit" +msgstr "修订上次æ交" + +#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "é‡æ–°æ‰«æ" + +#: git-gui.sh:1953 +#, fuzzy +msgid "Stage To Commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:1958 +#, fuzzy +msgid "Stage Changed Files To Commit" +msgstr "将被æ交的修改" + +#: git-gui.sh:1964 +msgid "Unstage From Commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:1969 lib/index.tcl:352 +msgid "Revert Changes" +msgstr "æ¢å¤ä¿®æ”¹" + +#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +msgid "Sign Off" +msgstr "ç¾å" + +#: git-gui.sh:1980 git-gui.sh:2296 +#, fuzzy +msgid "Commit@@verb" +msgstr "æ交" + +#: git-gui.sh:1991 +msgid "Local Merge..." +msgstr "本地åˆå¹¶..." + +#: git-gui.sh:1996 +msgid "Abort Merge..." +msgstr "å–消åˆå¹¶..." + +#: git-gui.sh:2008 +msgid "Push..." +msgstr "ä¸Šä¼ ..." + +#: git-gui.sh:2019 lib/choose_repository.tcl:41 +msgid "Apple" +msgstr "苹果" + +#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "关于%s" + +#: git-gui.sh:2026 +msgid "Preferences..." +msgstr "" + +#: git-gui.sh:2034 git-gui.sh:2558 +msgid "Options..." +msgstr "选项..." + +#: git-gui.sh:2040 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "帮助" + +#: git-gui.sh:2081 +msgid "Online Documentation" +msgstr "在线文档" + +#: git-gui.sh:2165 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" + +#: git-gui.sh:2198 +msgid "Current Branch:" +msgstr "当å‰åˆ†æ”¯:" + +#: git-gui.sh:2219 +#, fuzzy +msgid "Staged Changes (Will Commit)" +msgstr "将被æ交的修改" + +#: git-gui.sh:2239 +msgid "Unstaged Changes" +msgstr "" + +#: git-gui.sh:2286 +msgid "Stage Changed" +msgstr "" + +#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "ä¸Šä¼ " + +#: git-gui.sh:2332 +msgid "Initial Commit Message:" +msgstr "åˆå§‹æ交æè¿°:" + +#: git-gui.sh:2333 +msgid "Amended Commit Message:" +msgstr "修订æ交æè¿°:" + +#: git-gui.sh:2334 +msgid "Amended Initial Commit Message:" +msgstr "修订åˆå§‹æ交æè¿°:" + +#: git-gui.sh:2335 +msgid "Amended Merge Commit Message:" +msgstr "修订åˆå¹¶æ交æè¿°:" + +#: git-gui.sh:2336 +msgid "Merge Commit Message:" +msgstr "åˆå¹¶æ交æè¿°:" + +#: git-gui.sh:2337 +msgid "Commit Message:" +msgstr "æ交æè¿°:" + +#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +msgid "Copy All" +msgstr "全部å¤åˆ¶" + +#: git-gui.sh:2406 lib/blame.tcl:104 +msgid "File:" +msgstr "" + +#: git-gui.sh:2508 +msgid "Refresh" +msgstr "刷新" + +#: git-gui.sh:2529 +msgid "Apply/Reverse Hunk" +msgstr "应用/撤消æ¤ä¿®æ”¹å—" + +#: git-gui.sh:2535 +msgid "Decrease Font Size" +msgstr "缩å°å—体" + +#: git-gui.sh:2539 +msgid "Increase Font Size" +msgstr "放大å—体" + +#: git-gui.sh:2544 +msgid "Show Less Context" +msgstr "显示更多diff上下文" + +#: git-gui.sh:2551 +msgid "Show More Context" +msgstr "显示更少diff上下文" + +#: git-gui.sh:2565 +#, fuzzy +msgid "Unstage Hunk From Commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:2567 +#, fuzzy +msgid "Stage Hunk For Commit" +msgstr "从本次æ交移除" + +#: git-gui.sh:2586 +msgid "Initializing..." +msgstr "" + +#: git-gui.sh:2677 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" + +#: git-gui.sh:2707 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" + +#: git-gui.sh:2712 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" + +#: lib/about.tcl:25 +msgid "git-gui - a graphical user interface for Git." +msgstr "" + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "" + +#: lib/blame.tcl:81 +#, fuzzy +msgid "Commit:" +msgstr "æ交" + +#: lib/blame.tcl:249 +#, fuzzy +msgid "Copy Commit" +msgstr "æ交" + +#: lib/blame.tcl:369 +#, tcl-format +msgid "Reading %s..." +msgstr "" + +#: lib/blame.tcl:473 +msgid "Loading copy/move tracking annotations..." +msgstr "" + +#: lib/blame.tcl:493 +msgid "lines annotated" +msgstr "" + +#: lib/blame.tcl:674 +msgid "Loading original location annotations..." +msgstr "" + +#: lib/blame.tcl:677 +msgid "Annotation complete." +msgstr "" + +#: lib/blame.tcl:731 +msgid "Loading annotation..." +msgstr "" + +#: lib/blame.tcl:787 +msgid "Author:" +msgstr "" + +#: lib/blame.tcl:791 +#, fuzzy +msgid "Committer:" +msgstr "æ交" + +#: lib/blame.tcl:796 +msgid "Original File:" +msgstr "" + +#: lib/blame.tcl:910 +msgid "Originally By:" +msgstr "" + +#: lib/blame.tcl:916 +msgid "In File:" +msgstr "" + +#: lib/blame.tcl:921 +msgid "Copied Or Moved Here By:" +msgstr "" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +#, fuzzy +msgid "Checkout Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/branch_checkout.tcl:23 +#, fuzzy +msgid "Checkout" +msgstr "切æ¢..." + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:286 +msgid "Revision" +msgstr "" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 +#, fuzzy +msgid "Options" +msgstr "选项..." + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "" + +#: lib/branch_create.tcl:22 +#, fuzzy +msgid "Create Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/branch_create.tcl:27 +#, fuzzy +msgid "Create New Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 +#, fuzzy +msgid "Create" +msgstr "新建..." + +#: lib/branch_create.tcl:40 +#, fuzzy +msgid "Branch Name" +msgstr "分支" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +#, fuzzy +msgid "Reset" +msgstr "é‡ç½®æ‰€æœ‰ä¿®åŠ¨..." + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "" + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "" + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "" + +#: lib/branch_delete.tcl:15 +#, fuzzy +msgid "Delete Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "" + +#: lib/branch_delete.tcl:37 +#, fuzzy +msgid "Local Branches" +msgstr "分支" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +#, fuzzy +msgid "Rename Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/branch_rename.tcl:26 +#, fuzzy +msgid "Rename" +msgstr "改å..." + +#: lib/branch_rename.tcl:36 +#, fuzzy +msgid "Branch:" +msgstr "分支" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "" + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "" + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "" + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "" + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "" + +#: lib/browser.tcl:125 lib/browser.tcl:142 +#, tcl-format +msgid "Loading %s..." +msgstr "" + +#: lib/browser.tcl:186 +msgid "[Up To Parent]" +msgstr "" + +#: lib/browser.tcl:266 lib/browser.tcl:272 +#, fuzzy +msgid "Browse Branch Files" +msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶" + +#: lib/browser.tcl:277 lib/choose_repository.tcl:215 +#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 +#: lib/choose_repository.tcl:811 +msgid "Browse" +msgstr "" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "" + +#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 +msgid "Close" +msgstr "" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "" + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "" + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "" + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "" + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "" + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "" + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" + +#: lib/checkout_op.tcl:446 +#, fuzzy, tcl-format +msgid "Checked out '%s'." +msgstr "切æ¢..." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "" + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:164 +msgid "Visualize" +msgstr "" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" + +#: lib/choose_font.tcl:39 +#, fuzzy +msgid "Select" +msgstr "全选" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "" + +#: lib/choose_font.tcl:73 +#, fuzzy +msgid "Font Size" +msgstr "缩å°å—体" + +#: lib/choose_font.tcl:90 +msgid "Font Example" +msgstr "" + +#: lib/choose_font.tcl:101 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" + +#: lib/choose_repository.tcl:25 +msgid "Git Gui" +msgstr "" + +#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 +#, fuzzy +msgid "Create New Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 +#, fuzzy +msgid "Clone Existing Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 +#, fuzzy +msgid "Open Existing Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/choose_repository.tcl:91 +msgid "Next >" +msgstr "" + +#: lib/choose_repository.tcl:152 +#, tcl-format +msgid "Location %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 +#: lib/choose_repository.tcl:172 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "" + +#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +msgid "Directory:" +msgstr "" + +#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 +#: lib/choose_repository.tcl:834 +#, fuzzy +msgid "Git Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#, tcl-format +msgid "Directory %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:265 +#, tcl-format +msgid "File %s already exists." +msgstr "" + +#: lib/choose_repository.tcl:286 +msgid "Clone" +msgstr "" + +#: lib/choose_repository.tcl:299 +msgid "URL:" +msgstr "" + +#: lib/choose_repository.tcl:319 +msgid "Clone Type:" +msgstr "" + +#: lib/choose_repository.tcl:325 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "" + +#: lib/choose_repository.tcl:331 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "" + +#: lib/choose_repository.tcl:337 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "" + +#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 +#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 +#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "" + +#: lib/choose_repository.tcl:405 +msgid "Standard only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:409 +msgid "Shared only available for local repository." +msgstr "" + +#: lib/choose_repository.tcl:439 +msgid "Failed to configure origin" +msgstr "" + +#: lib/choose_repository.tcl:451 +msgid "Counting objects" +msgstr "" + +#: lib/choose_repository.tcl:452 +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:476 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "" + +#: lib/choose_repository.tcl:512 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "" + +#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:740 +msgid "The 'master' branch has not been initialized." +msgstr "" + +#: lib/choose_repository.tcl:527 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "" + +#: lib/choose_repository.tcl:539 +#, tcl-format +msgid "Cloning from %s" +msgstr "" + +#: lib/choose_repository.tcl:570 +#, fuzzy +msgid "Copying objects" +msgstr "压缩数æ®åº“" + +#: lib/choose_repository.tcl:571 +msgid "KiB" +msgstr "" + +#: lib/choose_repository.tcl:595 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "" + +#: lib/choose_repository.tcl:605 +msgid "Linking objects" +msgstr "" + +#: lib/choose_repository.tcl:606 +msgid "objects" +msgstr "" + +#: lib/choose_repository.tcl:614 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "" + +#: lib/choose_repository.tcl:669 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:680 +msgid "Cannot fetch tags. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:704 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" + +#: lib/choose_repository.tcl:713 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "" + +#: lib/choose_repository.tcl:719 +msgid "Clone failed." +msgstr "" + +#: lib/choose_repository.tcl:726 +msgid "No default branch obtained." +msgstr "" + +#: lib/choose_repository.tcl:737 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "" + +#: lib/choose_repository.tcl:749 +msgid "Creating working directory" +msgstr "" + +#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 +#: lib/index.tcl:149 +msgid "files" +msgstr "" + +#: lib/choose_repository.tcl:779 +msgid "Initial file checkout failed." +msgstr "" + +#: lib/choose_repository.tcl:795 +msgid "Open" +msgstr "" + +#: lib/choose_repository.tcl:805 +#, fuzzy +msgid "Repository:" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/choose_repository.tcl:854 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "" + +#: lib/choose_rev.tcl:74 +#, fuzzy +msgid "Local Branch" +msgstr "分支" + +#: lib/choose_rev.tcl:79 +#, fuzzy +msgid "Tracking Branch" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 +msgid "Tag" +msgstr "" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "" + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "" + +#: lib/choose_rev.tcl:530 +msgid "Updated" +msgstr "" + +#: lib/choose_rev.tcl:558 +msgid "URL" +msgstr "" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentance what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" + +#: lib/commit.tcl:257 +msgid "write-tree failed:" +msgstr "" + +#: lib/commit.tcl:275 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "" + +#: lib/commit.tcl:279 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" + +#: lib/commit.tcl:286 +msgid "No changes to commit." +msgstr "" + +#: lib/commit.tcl:303 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:317 +msgid "commit-tree failed:" +msgstr "" + +#: lib/commit.tcl:339 +msgid "update-ref failed:" +msgstr "" + +#: lib/commit.tcl:430 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "" + +#: lib/console.tcl:57 +msgid "Working... please wait..." +msgstr "" + +#: lib/console.tcl:183 +msgid "Success" +msgstr "" + +#: lib/console.tcl:196 +msgid "Error: Command Failed" +msgstr "" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "" + +#: lib/database.tcl:72 +#, fuzzy +msgid "Compressing the object database" +msgstr "压缩数æ®åº“" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "" + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "" + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "" + +#: lib/diff.tcl:302 +msgid "Failed to unstage selected hunk." +msgstr "" + +#: lib/diff.tcl:309 +msgid "Failed to stage selected hunk." +msgstr "" + +#: lib/error.tcl:12 lib/error.tcl:102 +msgid "error" +msgstr "" + +#: lib/error.tcl:28 +msgid "warning" +msgstr "" + +#: lib/error.tcl:81 +msgid "You must correct the above errors before committing." +msgstr "" + +#: lib/index.tcl:241 +#, fuzzy, tcl-format +msgid "Unstaging %s from commit" +msgstr "从本次æ交移除" + +#: lib/index.tcl:285 +#, tcl-format +msgid "Adding %s" +msgstr "" + +#: lib/index.tcl:340 +#, fuzzy, tcl-format +msgid "Revert changes in file %s?" +msgstr "æ¢å¤ä¿®æ”¹" + +#: lib/index.tcl:342 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "" + +#: lib/index.tcl:348 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" + +#: lib/index.tcl:351 +msgid "Do Nothing" +msgstr "" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s" +msgstr "" + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "" + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "" + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "" + +#: lib/merge.tcl:266 +msgid "Abort failed." +msgstr "" + +#: lib/merge.tcl:268 +msgid "Abort completed. Ready." +msgstr "" + +#: lib/option.tcl:82 +msgid "Restore Defaults" +msgstr "" + +#: lib/option.tcl:86 +msgid "Save" +msgstr "" + +#: lib/option.tcl:96 +#, fuzzy, tcl-format +msgid "%s Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/option.tcl:97 +msgid "Global (All Repositories)" +msgstr "" + +#: lib/option.tcl:103 +msgid "User Name" +msgstr "" + +#: lib/option.tcl:104 +msgid "Email Address" +msgstr "" + +#: lib/option.tcl:106 +#, fuzzy +msgid "Summarize Merge Commits" +msgstr "修订åˆå¹¶æ交æè¿°:" + +#: lib/option.tcl:107 +msgid "Merge Verbosity" +msgstr "" + +#: lib/option.tcl:108 +msgid "Show Diffstat After Merge" +msgstr "" + +#: lib/option.tcl:110 +msgid "Trust File Modification Timestamps" +msgstr "" + +#: lib/option.tcl:111 +msgid "Prune Tracking Branches During Fetch" +msgstr "" + +#: lib/option.tcl:112 +msgid "Match Tracking Branches" +msgstr "" + +#: lib/option.tcl:113 +msgid "Number of Diff Context Lines" +msgstr "" + +#: lib/option.tcl:114 +msgid "New Branch Name Template" +msgstr "" + +#: lib/option.tcl:176 +msgid "Change Font" +msgstr "" + +#: lib/option.tcl:180 +#, tcl-format +msgid "Choose %s" +msgstr "" + +#: lib/option.tcl:186 +msgid "pt." +msgstr "" + +#: lib/option.tcl:200 +msgid "Preferences" +msgstr "" + +#: lib/option.tcl:235 +msgid "Failed to completely save options:" +msgstr "" + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "" + +#: lib/remote.tcl:170 +#, fuzzy +msgid "Fetch from" +msgstr "导入" + +#: lib/remote.tcl:213 +#, fuzzy +msgid "Push to" +msgstr "ä¸Šä¼ " + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "" + +#: lib/remote_branch_delete.tcl:47 +#, fuzzy +msgid "From Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +msgid "Arbitrary URL:" +msgstr "" + +#: lib/remote_branch_delete.tcl:84 +#, fuzzy +msgid "Branches" +msgstr "分支" + +#: lib/remote_branch_delete.tcl:109 +#, fuzzy +msgid "Delete Only If" +msgstr "åˆ é™¤" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "" + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "" + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "" + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "" + +#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 +msgid "Cannot write script:" +msgstr "" + +#: lib/shortcut.tcl:149 +msgid "Cannot write icon:" +msgstr "" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "" + +#: lib/transport.tcl:6 +#, fuzzy, tcl-format +msgid "fetch %s" +msgstr "导入" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "" + +#: lib/transport.tcl:89 +#, fuzzy +msgid "Push Branches" +msgstr "分支" + +#: lib/transport.tcl:103 +#, fuzzy +msgid "Source Branches" +msgstr "当å‰åˆ†æ”¯:" + +#: lib/transport.tcl:120 +#, fuzzy +msgid "Destination Repository" +msgstr "ç‰ˆæœ¬æ ‘" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "" + +#~ msgid "Add To Commit" +#~ msgstr "æ·»åŠ åˆ°æœ¬æ¬¡æ交" + +#~ msgid "Add Existing To Commit" +#~ msgstr "æ·»åŠ é»˜è®¤ä¿®æ”¹æ–‡ä»¶" + +#~ msgid "Unstaged Changes (Will Not Be Committed)" +#~ msgstr "ä¸è¢«æ交的修改" + +#~ msgid "Add Existing" +#~ msgstr "æ·»åŠ é»˜è®¤ä¿®æ”¹æ–‡ä»¶" + +#, fuzzy +#~ msgid "Push to %s..." +#~ msgstr "ä¸Šä¼ ..." diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh new file mode 100644 index 0000000000..98f32c0a07 --- /dev/null +++ b/git-gui/windows/git-gui.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec wish "$0" -- "$@" + +if { $argc >=2 && [lindex $argv 0] == "--working-dir" } { + cd [lindex $argv 1] + set argv [lrange $argv 2 end] + incr argc -2 +} + +set gitguidir [file dirname [info script]] +regsub -all ";" $gitguidir "\\;" gitguidir +set env(PATH) "$gitguidir;$env(PATH)" +unset gitguidir + +source [file join [file dirname [info script]] git-gui.tcl] diff --git a/git-instaweb.sh b/git-instaweb.sh index 2ca487d7d5..8503ae4030 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -2,9 +2,21 @@ # # Copyright (c) 2006 Eric Wong # -USAGE='[--start] [--stop] [--restart] - [--local] [--httpd=<httpd>] [--port=<port>] [--browser=<browser>] - [--module-path=<path> (for Apache2 only)]' + +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-instaweb [options] (--start | --stop | --restart) +-- +l,local only bind on 127.0.0.1 +p,port= the port to bind to +d,httpd= the command to launch +b,browser= the browser to launch +m,module-path= the module path (only needed for apache2) + Action +stop stop the web server +start start the web server +restart restart the web server +" . git-sh-setup @@ -36,7 +48,9 @@ start_httpd () { else # many httpds are installed in /usr/sbin or /usr/local/sbin # these days and those are not in most users $PATHs - for i in /usr/local/sbin /usr/sbin + # in addition, we may have generated a server script + # in $fqgitdir/gitweb. + for i in /usr/local/sbin /usr/sbin "$fqgitdir/gitweb" do if test -x "$i/$httpd_only" then @@ -76,52 +90,26 @@ do start_httpd exit 0 ;; - --local|-l) + -l|--local) local=true ;; - -d|--httpd|--httpd=*) - case "$#,$1" in - *,*=*) - httpd=`expr "$1" : '-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - httpd="$2" - shift ;; - esac + -d|--httpd) + shift + httpd="$1" + ;; + -b|--browser) + shift + browser="$1" ;; - -b|--browser|--browser=*) - case "$#,$1" in - *,*=*) - browser=`expr "$1" : '-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - browser="$2" - shift ;; - esac + -p|--port) + shift + port="$1" ;; - -p|--port|--port=*) - case "$#,$1" in - *,*=*) - port=`expr "$1" : '-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - port="$2" - shift ;; - esac + -m|--module-path) + shift + module_path="$1" ;; - -m|--module-path=*|--module-path) - case "$#,$1" in - *,*=*) - module_path=`expr "$1" : '-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - module_path="$2" - shift ;; - esac + --) ;; *) usage @@ -136,6 +124,43 @@ GIT_DIR="$fqgitdir" export GIT_EXEC_PATH GIT_DIR +webrick_conf () { + # generate a standalone server script in $fqgitdir/gitweb. + cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF +require 'webrick' +require 'yaml' +options = YAML::load_file(ARGV[0]) +options[:StartCallback] = proc do + File.open(options[:PidFile],"w") do |f| + f.puts Process.pid + end +end +options[:ServerType] = WEBrick::Daemon +server = WEBrick::HTTPServer.new(options) +['INT', 'TERM'].each do |signal| + trap(signal) {server.shutdown} +end +server.start +EOF + # generate a shell script to invoke the above ruby script, + # which assumes _ruby_ is in the user's $PATH. that's _one_ + # portable way to run ruby, which could be installed anywhere, + # really. + cat >"$fqgitdir/gitweb/$httpd" <<EOF +#!/bin/sh +exec ruby "$fqgitdir/gitweb/$httpd.rb" \$* +EOF + chmod +x "$fqgitdir/gitweb/$httpd" + + cat >"$conf" <<EOF +:Port: $port +:DocumentRoot: "$fqgitdir/gitweb" +:DirectoryIndex: ["gitweb.cgi"] +:PidFile: "$fqgitdir/pid" +EOF + test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf" +} + lighttpd_conf () { cat > "$conf" <<EOF server.document-root = "$fqgitdir/gitweb" @@ -236,6 +261,9 @@ case "$httpd" in *apache2*) apache2_conf ;; +webrick) + webrick_conf + ;; *) echo "Unknown httpd specified: $httpd" exit 1 diff --git a/git-lost-found.sh b/git-lost-found.sh index c0b00e0fd1..9cedaf80ce 100755 --- a/git-lost-found.sh +++ b/git-lost-found.sh @@ -2,8 +2,11 @@ USAGE='' SUBDIRECTORY_OK='Yes' +OPTIONS_SPEC= . git-sh-setup +echo "WARNING: '$0' is deprecated in favor of 'git fsck --lost-found'" >&2 + if [ "$#" != "0" ] then usage diff --git a/git-merge.sh b/git-merge.sh index cde09d4d60..1c123a37e6 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,7 +3,19 @@ # Copyright (c) 2005 Junio C Hamano # -USAGE='[-n] [--summary] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+' +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-merge [options] <remote>... +git-merge [options] <msg> HEAD <remote> +-- +summary show a diffstat at the end of the merge +n,no-summary don't show a diffstat at the end of the merge +squash create a single commit instead of doing a merge +commit perform a commit if the merge sucesses (default) +ff allow fast forward (default) +s,strategy= merge strategy to use +m,message= message to be used for the merge commit (if any) +" SUBDIRECTORY_OK=Yes . git-sh-setup @@ -28,20 +40,19 @@ allow_trivial_merge=t dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ - "$GIT_DIR/MERGE_SAVE" || exit 1 + "$GIT_DIR/MERGE_STASH" || exit 1 } savestate() { # Stash away any local modifications. - git diff-index -z --name-only $head | - cpio -0 -o >"$GIT_DIR/MERGE_SAVE" + git stash create >"$GIT_DIR/MERGE_STASH" } restorestate() { - if test -f "$GIT_DIR/MERGE_SAVE" + if test -f "$GIT_DIR/MERGE_STASH" then git reset --hard $head >/dev/null - cpio -iuv <"$GIT_DIR/MERGE_SAVE" + git stash apply $(cat "$GIT_DIR/MERGE_STASH") git update-index --refresh >/dev/null fi } @@ -59,7 +70,7 @@ finish_up_to_date () { squash_message () { echo Squashed commit of the following: echo - git log --no-merges ^"$head" $remote + git log --no-merges ^"$head" $remoteheads } finish () { @@ -82,6 +93,7 @@ finish () { ;; *) git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1 + git gc --auto ;; esac ;; @@ -97,6 +109,19 @@ finish () { fi ;; esac + + # Run a post-merge hook + if test -x "$GIT_DIR"/hooks/post-merge + then + case "$squash" in + t) + "$GIT_DIR"/hooks/post-merge 1 + ;; + '') + "$GIT_DIR"/hooks/post-merge 0 + ;; + esac + fi } merge_name () { @@ -119,57 +144,64 @@ merge_name () { fi } -case "$#" in 0) usage ;; esac - -have_message= -while test $# != 0 -do - case "$1" in - -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\ - --no-summa|--no-summar|--no-summary) - show_diffstat=false ;; - --summary) - show_diffstat=t ;; - --sq|--squ|--squa|--squas|--squash) - squash=t no_commit=t ;; - --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit) - no_commit=t ;; - -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ - --strateg=*|--strategy=*|\ - -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) - case "$#,$1" in - *,*=*) - strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - strategy="$2" - shift ;; - esac - case " $all_strategies " in - *" $strategy "*) - use_strategies="$use_strategies$strategy " ;; - *) - die "available strategies are: $all_strategies" ;; +parse_config () { + while test $# != 0; do + case "$1" in + -n|--no-summary) + show_diffstat=false ;; + --summary) + show_diffstat=t ;; + --squash) + allow_fast_forward=t squash=t no_commit=t ;; + --no-squash) + allow_fast_forward=t squash= no_commit= ;; + --commit) + allow_fast_forward=t squash= no_commit= ;; + --no-commit) + allow_fast_forward=t squash= no_commit=t ;; + --ff) + allow_fast_forward=t squash= no_commit= ;; + --no-ff) + allow_fast_forward=false squash= no_commit= ;; + -s|--strategy) + shift + case " $all_strategies " in + *" $1 "*) + use_strategies="$use_strategies$1 " ;; + *) + die "available strategies are: $all_strategies" ;; + esac + ;; + -m|--message) + shift + merge_msg="$1" + have_message=t + ;; + --) + shift + break ;; + *) usage ;; esac - ;; - -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) - merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'` - have_message=t - ;; - -m|--m|--me|--mes|--mess|--messa|--messag|--message) shift - case "$#" in - 1) usage ;; - esac - merge_msg="$1" - have_message=t - ;; - -*) usage ;; - *) break ;; - esac - shift -done + done + args_left=$# +} + +test $# != 0 || usage + +have_message= + +if branch=$(git-symbolic-ref -q HEAD) +then + mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions") + if test -n "$mergeopts" + then + parse_config $mergeopts -- + fi +fi + +parse_config "$@" +while test $args_left -lt $#; do shift; done if test -z "$show_diffstat"; then test "$(git config --bool merge.diffstat)" = false && show_diffstat=false @@ -386,7 +418,7 @@ case "$use_strategies" in single_strategy=no ;; *) - rm -f "$GIT_DIR/MERGE_SAVE" + rm -f "$GIT_DIR/MERGE_STASH" single_strategy=yes ;; esac @@ -444,7 +476,13 @@ done # auto resolved the merge cleanly. if test '' != "$result_tree" then - parents=$(git show-branch --independent "$head" "$@" | sed -e 's/^/-p /') + if test "$allow_fast_forward" = "t" + then + parents=$(git show-branch --independent "$head" "$@") + else + parents=$(git rev-parse "$head" "$@") + fi + parents=$(echo "$parents" | sed -e 's/^/-p /') result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit finish "$result_commit" "Merge made by $wt_strategy." dropsave diff --git a/git-mergetool.sh b/git-mergetool.sh index 9f4f3134b6..5587c5ecea 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -10,6 +10,7 @@ USAGE='[--tool=tool] [file to merge] ...' SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= . git-sh-setup require_work_tree prefix=$(git rev-parse --show-prefix) @@ -192,10 +193,10 @@ merge_file () { case "$merge_tool" in kdiff3) if base_present ; then - (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) else - (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) fi status=$? @@ -203,35 +204,35 @@ merge_file () { ;; tkdiff) if base_present ; then - tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" else - tkdiff -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE" fi status=$? save_backup ;; meld|vimdiff) touch "$BACKUP" - $merge_tool -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; gvimdiff) touch "$BACKUP" - gvimdiff -f -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; xxdiff) touch "$BACKUP" if base_present ; then - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE" else - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ @@ -243,18 +244,28 @@ merge_file () { opendiff) touch "$BACKUP" if base_present; then - opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat else - opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat + fi + check_unchanged + save_backup + ;; + ecmerge) + touch "$BACKUP" + if base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path" + else + "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path" fi check_unchanged save_backup ;; emerge) if base_present ; then - emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" else - emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" fi status=$? save_backup @@ -297,17 +308,38 @@ do shift done +valid_tool() { + case "$1" in + kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + ;; # happy + *) + return 1 + ;; + esac +} + +init_merge_tool_path() { + merge_tool_path=`git config mergetool.$1.path` + if test -z "$merge_tool_path" ; then + case "$1" in + emerge) + merge_tool_path=emacs + ;; + *) + merge_tool_path=$1 + ;; + esac + fi +} + + if test -z "$merge_tool"; then merge_tool=`git config merge.tool` - case "$merge_tool" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "") - ;; # happy - *) + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" echo >&2 "Resetting to default..." unset merge_tool - ;; - esac + fi fi if test -z "$merge_tool" ; then @@ -329,40 +361,30 @@ if test -z "$merge_tool" ; then merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" echo "merge tool candidates: $merge_tool_candidates" for i in $merge_tool_candidates; do - if test $i = emerge ; then - cmd=emacs - else - cmd=$i - fi - if type $cmd > /dev/null 2>&1; then + init_merge_tool_path $i + if type "$merge_tool_path" > /dev/null 2>&1; then merge_tool=$i break fi done if test -z "$merge_tool" ; then - echo "No available merge resolution programs available." + echo "No known merge resolution program available." exit 1 fi +else + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge_tool $merge_tool" + exit 1 + fi + + init_merge_tool_path "$merge_tool" + + if ! type "$merge_tool_path" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available as '$merge_tool_path'" + exit 1 + fi fi -case "$merge_tool" in - kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff) - if ! type "$merge_tool" > /dev/null 2>&1; then - echo "The merge tool $merge_tool is not available" - exit 1 - fi - ;; - emerge) - if ! type "emacs" > /dev/null 2>&1; then - echo "Emacs is not available" - exit 1 - fi - ;; - *) - echo "Unknown merge tool: $merge_tool" - exit 1 - ;; -esac if test $# -eq 0 ; then files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` diff --git a/git-pull.sh b/git-pull.sh index 74bfc16744..30fdc57310 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -4,9 +4,10 @@ # # Fetch one or more remote refs and merge it/them into the current HEAD. -USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...' +USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= . git-sh-setup set_reflog_action "pull $*" require_work_tree @@ -15,7 +16,7 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= no_summary= no_commit= squash= +strategy_args= no_summary= no_commit= squash= no_ff= while : do case "$1" in @@ -27,8 +28,16 @@ do ;; --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit) no_commit=--no-commit ;; + --c|--co|--com|--comm|--commi|--commit) + no_commit=--commit ;; --sq|--squ|--squa|--squas|--squash) squash=--squash ;; + --no-sq|--no-squ|--no-squa|--no-squas|--no-squash) + squash=--no-squash ;; + --ff) + no_ff=--ff ;; + --no-ff) + no_ff=--no-ff ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -133,5 +142,5 @@ then fi merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit -exec git-merge $no_summary $no_commit $squash $strategy_args \ +exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 880c81d121..6b0c4d2f27 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -1,5 +1,12 @@ #!/bin/sh -USAGE='--dry-run --author <author> --patches </path/to/quilt/patch/directory>' +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-quiltimport [options] +-- +n,dry-run dry run +author= author name and email address for patches without any +patches= path to the quilt series and patches +" SUBDIRECTORY_ON=Yes . git-sh-setup @@ -8,39 +15,25 @@ quilt_author="" while test $# != 0 do case "$1" in - --au=*|--aut=*|--auth=*|--autho=*|--author=*) - quilt_author=$(expr "z$1" : 'z-[^=]*\(.*\)') - shift - ;; - - --au|--aut|--auth|--autho|--author) - case "$#" in 1) usage ;; esac + --author) shift quilt_author="$1" - shift ;; - - --dry-run) - shift + -n|--dry-run) dry_run=1 ;; - - --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*) - QUILT_PATCHES=$(expr "z$1" : 'z-[^=]*\(.*\)') - shift - ;; - - --pa|--pat|--patc|--patch|--patche|--patches) - case "$#" in 1) usage ;; esac + --patches) shift QUILT_PATCHES="$1" - shift ;; - + --) + shift + break;; *) - break + usage ;; esac + shift done # Quilt Author diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index ff38a22edf..e9cd6fd999 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -13,6 +13,7 @@ USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose] [--onto <branch>] <upstream> [<branch>])' +OPTIONS_SPEC= . git-sh-setup require_work_tree @@ -36,14 +37,14 @@ warn () { output () { case "$VERBOSE" in '') - "$@" > "$DOTEST"/output 2>&1 + output=$("$@" 2>&1 ) status=$? - test $status != 0 && - cat "$DOTEST"/output + test $status != 0 && printf "%s\n" "$output" return $status - ;; + ;; *) "$@" + ;; esac } @@ -63,6 +64,7 @@ comment_for_reflog () { ''|rebase*) GIT_REFLOG_ACTION="rebase -i ($1)" export GIT_REFLOG_ACTION + ;; esac } @@ -70,22 +72,23 @@ mark_action_done () { sed -e 1q < "$TODO" >> "$DONE" sed -e 1d < "$TODO" >> "$TODO".new mv -f "$TODO".new "$TODO" - count=$(($(wc -l < "$DONE"))) - total=$(($count+$(wc -l < "$TODO"))) + count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l))) + total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l))) printf "Rebasing (%d/%d)\r" $count $total test -z "$VERBOSE" || echo } make_patch () { - parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null) + parent_sha1=$(git rev-parse --verify "$1"^) || + die "Cannot get patch for $1^" git diff-tree -p "$parent_sha1".."$1" > "$DOTEST"/patch + test -f "$DOTEST"/message || + git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message + test -f "$DOTEST"/author-script || + get_author_ident_from_commit "$1" > "$DOTEST"/author-script } die_with_patch () { - test -f "$DOTEST"/message || - git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message - test -f "$DOTEST"/author-script || - get_author_ident_from_commit $sha1 > "$DOTEST"/author-script make_patch "$1" die "$2" } @@ -95,15 +98,20 @@ die_abort () { die "$1" } +has_action () { + grep -vqe '^$' -e '^#' "$1" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null) + parent_sha1=$(git rev-parse --verify $sha1^) || + die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test $no_ff$current_sha1 = $parent_sha1; then + if test "$no_ff$current_sha1" = "$parent_sha1"; then output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) @@ -129,7 +137,7 @@ pick_one_preserving_merges () { fast_forward=t preserve=t new_parents= - for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-) + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) do if test -f "$REWRITTEN"/$p then @@ -141,43 +149,49 @@ pick_one_preserving_merges () { ;; # do nothing; that parent is already there *) new_parents="$new_parents $new_p" + ;; esac fi done case $fast_forward in t) output warn "Fast forward to $sha1" - test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1 + test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1 ;; f) test "a$1" = a-n && die "Refusing to squash a merge: $sha1" - first_parent=$(expr "$new_parents" : " \([^ ]*\)") + 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" echo $sha1 > "$DOTEST"/current-commit case "$new_parents" in - \ *\ *) + ' '*' '*) # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | \ - sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")" + msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} # NEEDSWORK: give rerere a chance - if ! output git merge $STRATEGY -m "$msg" $new_parents + if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + output git merge $STRATEGY -m "$msg" \ + $new_parents then - echo "$msg" > "$GIT_DIR"/MERGE_MSG + printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die Error redoing merge $sha1 fi ;; *) output git cherry-pick "$@" || die_with_patch $sha1 "Could not pick $sha1" + ;; esac + ;; esac } @@ -214,27 +228,28 @@ peek_next_command () { } do_next () { - test -f "$DOTEST"/message && rm "$DOTEST"/message - test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script + rm -f "$DOTEST"/message "$DOTEST"/author-script \ + "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in - \#|'') + '#'*|'') mark_action_done ;; - pick) + pick|p) comment_for_reflog pick mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" ;; - edit) + edit|e) comment_for_reflog edit mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 + : > "$DOTEST"/amend warn warn "You can amend the commit now, with" warn @@ -242,24 +257,25 @@ do_next () { warn exit 0 ;; - squash) + squash|s) comment_for_reflog squash - test -z "$(grep -ve '^$' -e '^#' < $DONE)" && + has_action "$DONE" || die "Cannot 'squash' without a previous commit" mark_action_done make_squash_message $sha1 > "$MSG" case "$(peek_next_command)" in - squash) + squash|s) EDIT_COMMIT= USE_OUTPUT=output cp "$MSG" "$SQUASH_MSG" - ;; + ;; *) EDIT_COMMIT=-e USE_OUTPUT= - test -f "$SQUASH_MSG" && rm "$SQUASH_MSG" + rm -f "$SQUASH_MSG" || exit + ;; esac failed=f @@ -271,7 +287,9 @@ do_next () { f) # This is like --amend, but with a different message eval "$author_script" - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT ;; t) @@ -279,11 +297,13 @@ do_next () { warn warn "Could not apply $sha1... $rest" die_with_patch $sha1 "" + ;; esac ;; *) warn "Unknown command: $command $sha1 $rest" die_with_patch $sha1 "Please fix this in the file $TODO." + ;; esac test -s "$TODO" && return @@ -300,13 +320,18 @@ do_next () { else NEWHEAD=$(git rev-parse HEAD) fi && - message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" && - git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && - git symbolic-ref HEAD $HEADNAME && { + case $HEADNAME in + refs/*) + message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" && + git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && + git symbolic-ref HEAD $HEADNAME + ;; + esac && { test ! -f "$DOTEST"/verbose || git diff-tree --stat $(cat "$DOTEST"/head)..HEAD } && rm -rf "$DOTEST" && + git gc --auto && warn "Successfully rebased and updated $HEADNAME." exit @@ -332,7 +357,9 @@ do git update-index --refresh && git diff-files --quiet && ! git diff-index --cached --quiet HEAD -- && - . "$DOTEST"/author-script && + . "$DOTEST"/author-script && { + test ! -f "$DOTEST"/amend || git reset --soft HEAD^ + } && export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE && git commit -F "$DOTEST"/message -e @@ -346,7 +373,11 @@ do HEADNAME=$(cat "$DOTEST"/head-name) HEAD=$(cat "$DOTEST"/head) - git symbolic-ref HEAD $HEADNAME && + case $HEADNAME in + refs/*) + git symbolic-ref HEAD $HEADNAME + ;; + esac && output git reset --hard $HEAD && rm -rf "$DOTEST" exit @@ -407,7 +438,6 @@ do require_clean_work_tree - mkdir "$DOTEST" || die "Could not create temporary $DOTEST" if test ! -z "$2" then output git show-ref --verify --quiet "refs/heads/$2" || @@ -419,11 +449,13 @@ do HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + mkdir "$DOTEST" || die "Could not create temporary $DOTEST" + test -z "$ONTO" && ONTO=$UPSTREAM : > "$DOTEST"/interactive || die "Could not mark as interactive" - git symbolic-ref HEAD > "$DOTEST"/head-name || - die "Could not get HEAD" + git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null || + echo "detached HEAD" > "$DOTEST"/head-name echo $HEAD > "$DOTEST"/head echo $UPSTREAM > "$DOTEST"/upstream @@ -453,8 +485,13 @@ do SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) SHORTHEAD=$(git rev-parse --short $HEAD) SHORTONTO=$(git rev-parse --short $ONTO) - cat > "$TODO" << EOF -# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO + git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ + --abbrev=7 --reverse --left-right --cherry-pick \ + $UPSTREAM...$HEAD | \ + sed -n "s/^>/pick /p" > "$TODO" + cat >> "$TODO" << EOF + +# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO # # Commands: # pick = use commit @@ -462,24 +499,22 @@ do # squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. +# However, if you remove everything, the rebase will be aborted. # EOF - git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ - --abbrev=7 --reverse --left-right --cherry-pick \ - $UPSTREAM...$HEAD | \ - sed -n "s/^>/pick /p" >> "$TODO" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" cp "$TODO" "$TODO".backup git_editor "$TODO" || die "Could not execute editor" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" output git checkout $ONTO && do_rest + ;; esac shift done diff --git a/git-rebase.sh b/git-rebase.sh index c9b284c751..bdcea0ed70 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -29,6 +29,7 @@ Example: git-rebase master~1 topic ' SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= . git-sh-setup set_reflog_action rebase require_work_tree @@ -87,7 +88,7 @@ call_merge () { cmt="$(cat "$dotest/cmt.$1")" echo "$cmt" > "$dotest/current" hd=$(git rev-parse --verify HEAD) - cmt_name=$(git symbolic-ref HEAD) + cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) msgnum=$(cat "$dotest/msgnum") end=$(cat "$dotest/end") eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' @@ -115,7 +116,24 @@ call_merge () { esac } +move_to_original_branch () { + test -z "$head_name" && + head_name="$(cat "$dotest"/head-name)" && + onto="$(cat "$dotest"/onto)" && + orig_head="$(cat "$dotest"/orig-head)" + case "$head_name" in + refs/*) + message="rebase finished: $head_name onto $onto" + git update-ref -m "$message" \ + $head_name $(git rev-parse HEAD) $orig_head && + git symbolic-ref HEAD $head_name || + die "Could not move back to $head_name" + ;; + esac +} + finish_rb_merge () { + move_to_original_branch rm -r "$dotest" echo "All done." } @@ -153,10 +171,15 @@ do finish_rb_merge exit fi - git am --resolved --3way --resolvemsg="$RESOLVEMSG" + head_name=$(cat .dotest/head-name) && + onto=$(cat .dotest/onto) && + orig_head=$(cat .dotest/orig-head) && + git am --resolved --3way --resolvemsg="$RESOLVEMSG" && + move_to_original_branch exit ;; --skip) + git reset --hard HEAD || exit $? if test -d "$dotest" then git rerere clear @@ -173,16 +196,23 @@ do finish_rb_merge exit fi - git am -3 --skip --resolvemsg="$RESOLVEMSG" + head_name=$(cat .dotest/head-name) && + onto=$(cat .dotest/onto) && + orig_head=$(cat .dotest/orig-head) && + git am -3 --skip --resolvemsg="$RESOLVEMSG" && + move_to_original_branch exit ;; --abort) git rerere clear if test -d "$dotest" then + move_to_original_branch rm -r "$dotest" elif test -d .dotest then + dotest=.dotest + move_to_original_branch rm -r .dotest else die "No rebase in progress?" @@ -215,9 +245,11 @@ do -v|--verbose) verbose=t ;; + --whitespace=*) + git_am_opt="$git_am_opt $1" + ;; -C*) - git_am_opt=$1 - shift + git_am_opt="$git_am_opt $1" ;; -*) usage @@ -316,6 +348,19 @@ then GIT_PAGER='' git diff --stat --summary "$mb" "$onto" fi +# move to a detached HEAD +orig_head=$(git rev-parse HEAD^0) +head_name=$(git symbolic-ref HEAD 2> /dev/null) +case "$head_name" in +'') + head_name="detached HEAD" + ;; +*) + git checkout "$orig_head" > /dev/null 2>&1 || + die "could not detach HEAD" + ;; +esac + # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD. echo "First, rewinding head to replay your work on top of it..." git-reset --hard "$onto" @@ -325,14 +370,21 @@ git-reset --hard "$onto" if test "$mb" = "$branch" then echo >&2 "Fast-forwarded $branch_name to $onto_name." + move_to_original_branch exit 0 fi if test -z "$do_merge" then git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD | - git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG" - exit $? + git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG" && + move_to_original_branch + ret=$? + test 0 != $ret -a -d .dotest && + echo $head_name > .dotest/head-name && + echo $onto > .dotest/onto && + echo $orig_head > .dotest/orig-head + exit $ret fi # start doing a rebase with git-merge @@ -341,8 +393,10 @@ fi mkdir -p "$dotest" echo "$onto" > "$dotest/onto" echo "$onto_name" > "$dotest/onto_name" -prev_head=`git rev-parse HEAD^0` +prev_head=$orig_head echo "$prev_head" > "$dotest/prev_head" +echo "$orig_head" > "$dotest/orig-head" +echo "$head_name" > "$dotest/head-name" msgnum=0 for cmt in `git rev-list --reverse --no-merges "$upstream"..ORIG_HEAD` diff --git a/git-remote.perl b/git-remote.perl index 11630b1a8b..d13e4c1fea 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -281,7 +281,9 @@ sub add_remote { for (@$track) { $git->command('config', '--add', "remote.$name.fetch", - "+refs/heads/$_:refs/remotes/$name/$_"); + $opts->{'mirror'} ? + "+refs/$_:refs/$_" : + "+refs/heads/$_:refs/remotes/$name/$_"); } if ($opts->{'fetch'}) { $git->command('fetch', $name); @@ -317,6 +319,34 @@ sub update_remote { } } +sub rm_remote { + my ($name) = @_; + if (!exists $remote->{$name}) { + print STDERR "No such remote $name\n"; + return 1; + } + + $git->command('config', '--remove-section', "remote.$name"); + + eval { + my @trackers = $git->command('config', '--get-regexp', + 'branch.*.remote', $name); + for (@trackers) { + /^branch\.(.*)?\.remote/; + $git->config('--unset', "branch.$1.remote"); + $git->config('--unset', "branch.$1.merge"); + } + }; + + my @refs = $git->command('for-each-ref', + '--format=%(refname) %(objectname)', "refs/remotes/$name"); + for (@refs) { + ($ref, $object) = split; + $git->command(qw(update-ref -d), $ref, $object); + } + return 0; +} + sub add_usage { print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n"; exit(1); @@ -416,6 +446,10 @@ elsif ($ARGV[0] eq 'add') { shift @ARGV; next; } + if ($opt eq '--mirror') { + $opts{'mirror'} = 1; + next; + } add_usage(); } if (@ARGV != 3) { @@ -423,9 +457,17 @@ elsif ($ARGV[0] eq 'add') { } add_remote($ARGV[1], $ARGV[2], \%opts); } +elsif ($ARGV[0] eq 'rm') { + if (@ARGV <= 1) { + print STDERR "Usage: git remote rm <remote>\n"; + exit(1); + } + exit(rm_remote($ARGV[1])); +} else { print STDERR "Usage: git remote\n"; print STDERR " git remote add <name> <url>\n"; + print STDERR " git remote rm <name>\n"; print STDERR " git remote show <name>\n"; print STDERR " git remote prune <name>\n"; print STDERR " git remote update [group]\n"; diff --git a/git-repack.sh b/git-repack.sh index 0aae1a3ed5..e18eb3f5dc 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -3,25 +3,41 @@ # Copyright (c) 2005 Linus Torvalds # -USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--max-pack-size=N] [--window=N] [--window-memory=N] [--depth=N]' +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-repack [options] +-- +a pack everything in a single pack +A same as -a, and keep unreachable objects too +d remove redundant packs, and run git-prune-packed +f pass --no-reuse-delta to git-pack-objects +q,quiet be quiet +l pass --local to git-pack-objects + Packing constraints +window= size of the window used for delta compression +window-memory= same as the above, but limit memory size instead of entries count +depth= limits the maximum delta depth +max-pack-size= maximum size of each packfile +" SUBDIRECTORY_OK='Yes' . git-sh-setup -no_update_info= all_into_one= remove_redundant= +no_update_info= all_into_one= remove_redundant= keep_unreachable= local= quiet= no_reuse= extra= while test $# != 0 do case "$1" in -n) no_update_info=t ;; -a) all_into_one=t ;; + -A) all_into_one=t + keep_unreachable=--keep-unreachable ;; -d) remove_redundant=t ;; -q) quiet=-q ;; -f) no_reuse=--no-reuse-object ;; -l) local=--local ;; - --max-pack-size=*) extra="$extra $1" ;; - --window=*) extra="$extra $1" ;; - --window-memory=*) extra="$extra $1" ;; - --depth=*) extra="$extra $1" ;; + --max-pack-size|--window|--window-memory|--depth) + extra="$extra $1=$2"; shift ;; + --) shift; break;; *) usage ;; esac shift @@ -59,7 +75,13 @@ case ",$all_into_one," in fi done fi - [ -z "$args" ] && args='--unpacked --incremental' + if test -z "$args" + then + args='--unpacked --incremental' + elif test -n "$keep_unreachable" + then + args="$args $keep_unreachable" + fi ;; esac @@ -75,9 +97,6 @@ for name in $names ; do fullbases="$fullbases pack-$name" chmod a-w "$PACKTMP-$name.pack" chmod a-w "$PACKTMP-$name.idx" - if test "$quiet" != '-q'; then - echo "Pack pack-$name created." - fi mkdir -p "$PACKDIR" || exit for sfx in pack idx diff --git a/git-request-pull.sh b/git-request-pull.sh index 95ad66630f..068f5e0fc7 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -8,6 +8,7 @@ USAGE='<commit> <url> [<head>]' LONG_USAGE='Summarizes the changes since <commit> to the standard output, and includes <url> in the message generated.' SUBDIRECTORY_OK='Yes' +OPTIONS_SPEC= . git-sh-setup . git-parse-remote diff --git a/git-send-email.perl b/git-send-email.perl index b03297c9d7..76baa8e431 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -73,11 +73,22 @@ Options: --signed-off-cc Automatically add email addresses that appear in Signed-off-by: or Cc: lines to the cc: list. Defaults to on. + --identity The configuration identity, a subsection to prioritise over + the default section. + --smtp-server If set, specifies the outgoing SMTP server to use. - Defaults to localhost. + Defaults to localhost. Port number can be specified here with + hostname:port format or by using --smtp-server-port option. + + --smtp-server-port Specify a port on the outgoing SMTP server to connect to. + + --smtp-user The username for SMTP-AUTH. - --suppress-from Suppress sending emails to yourself if your address - appears in a From: line. Defaults to off. + --smtp-pass The password for SMTP-AUTH. + + --smtp-ssl If set, connects to the SMTP server using SSL. + + --suppress-from Suppress sending emails to yourself. Defaults to off. --thread Specify that the "In-Reply-To:" header should be set on all emails. Defaults to on. @@ -134,6 +145,7 @@ sub format_2822_time { my $have_email_valid = eval { require Email::Valid; 1 }; my $smtp; +my $auth; sub unique_email_list(@); sub cleanup_compose_files(); @@ -145,7 +157,6 @@ my $compose_filename = ".msg.$$"; my (@to,@cc,@initial_cc,@bcclist,@xh, $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time); -my $smtp_server; my $envelope_sender; # Example reply to: @@ -164,24 +175,28 @@ my ($quiet, $dry_run) = (0, 0); # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd); +my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl); +my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); -my %config_settings = ( +my %config_bool_settings = ( "thread" => [\$thread, 1], "chainreplyto" => [\$chain_reply_to, 1], "suppressfrom" => [\$suppress_from, 0], "signedoffcc" => [\$signed_off_cc, 1], - "cccmd" => [\$cc_cmd, ""], + "smtpssl" => [\$smtp_ssl, 0], ); -foreach my $setting (keys %config_settings) { - my $config = $repo->config_bool("sendemail.$setting"); - ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1]; -} - -@bcclist = $repo->config('sendemail.bcc'); -if (!@bcclist or !$bcclist[0]) { - @bcclist = (); -} +my %config_settings = ( + "smtpserver" => \$smtp_server, + "smtpserverport" => \$smtp_server_port, + "smtpuser" => \$smtp_authuser, + "smtppass" => \$smtp_authpass, + "to" => \@to, + "cccmd" => \$cc_cmd, + "aliasfiletype" => \$aliasfiletype, + "bcc" => \@bcclist, + "aliasesfile" => \@alias_files, +); # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: @@ -194,6 +209,11 @@ my $rc = GetOptions("sender|from=s" => \$sender, "bcc=s" => \@bcclist, "chain-reply-to!" => \$chain_reply_to, "smtp-server=s" => \$smtp_server, + "smtp-server-port=s" => \$smtp_server_port, + "smtp-user=s" => \$smtp_authuser, + "smtp-pass=s" => \$smtp_authpass, + "smtp-ssl!" => \$smtp_ssl, + "identity=s" => \$identity, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, @@ -208,6 +228,43 @@ unless ($rc) { usage(); } +# Now, let's fill any that aren't set in with defaults: + +sub read_config { + my ($prefix) = @_; + + foreach my $setting (keys %config_bool_settings) { + my $target = $config_bool_settings{$setting}->[0]; + $$target = $repo->config_bool("$prefix.$setting") unless (defined $$target); + } + + foreach my $setting (keys %config_settings) { + my $target = $config_settings{$setting}; + if (ref($target) eq "ARRAY") { + unless (@$target) { + my @values = $repo->config("$prefix.$setting"); + @$target = @values if (@values && defined $values[0]); + } + } + else { + $$target = $repo->config("$prefix.$setting") unless (defined $$target); + } + } +} + +# read configuration from [sendemail "$identity"], fall back on [sendemail] +$identity = $repo->config("sendemail.identity") unless (defined $identity); +read_config("sendemail.$identity") if (defined $identity); +read_config("sendemail"); + +# fall back on builtin bool defaults +foreach my $setting (values %config_bool_settings) { + ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]})); +} + +my ($repoauthor) = $repo->ident_person('author'); +my ($repocommitter) = $repo->ident_person('committer'); + # Verify the user input foreach my $entry (@to) { @@ -222,14 +279,7 @@ foreach my $entry (@bcclist) { die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/; } -# Now, let's fill any that aren't set in with defaults: - -my ($repoauthor) = $repo->ident_person('author'); -my ($repocommitter) = $repo->ident_person('committer'); - my %aliases; -my @alias_files = $repo->config('sendemail.aliasesfile'); -my $aliasfiletype = $repo->config('sendemail.aliasfiletype'); my %parse_alias = ( # multiline formats can be supported in the future mutt => sub { my $fh = shift; while (<$fh>) { @@ -321,10 +371,7 @@ if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to =~ s/>?\s+$/>/; } -if (!$smtp_server) { - $smtp_server = $repo->config('sendemail.smtpserver'); -} -if (!$smtp_server) { +if (!defined $smtp_server) { foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { if (-x $_) { $smtp_server = $_; @@ -511,7 +558,11 @@ sub sanitize_address sub send_message { my @recipients = unique_email_list(@to); - @cc = (map { sanitize_address($_) } @cc); + @cc = (grep { my $cc = extract_valid_address($_); + not grep { $cc eq $_ } @recipients + } + map { sanitize_address($_) } + @cc); my $to = join (",\n\t", @recipients); @recipients = unique_email_list(@recipients,@cc,@bcclist); @recipients = (map { extract_valid_address($_) } @recipients); @@ -563,8 +614,30 @@ X-Mailer: git-send-email $gitversion print $sm "$header\n$message"; close $sm or die $?; } else { - require Net::SMTP; - $smtp ||= Net::SMTP->new( $smtp_server ); + + if (!defined $smtp_server) { + die "The required SMTP server is not properly defined." + } + + if ($smtp_ssl) { + $smtp_server_port ||= 465; # ssmtp + require Net::SMTP::SSL; + $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port); + } + else { + require Net::SMTP; + $smtp ||= Net::SMTP->new((defined $smtp_server_port) + ? "$smtp_server:$smtp_server_port" + : $smtp_server); + } + + if (!$smtp) { + die "Unable to initialize SMTP properly. Is there something wrong with your config?"; + } + + if ((defined $smtp_authuser) && (defined $smtp_authpass)) { + $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message; + } $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; $smtp->data or die $smtp->message; @@ -575,7 +648,7 @@ X-Mailer: git-send-email $gitversion if ($quiet) { printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); } else { - print (($dry_run ? "Dry-" : "")."OK. Log says:\nDate: $date\n"); + print (($dry_run ? "Dry-" : "")."OK. Log says:\n"); if ($smtp_server !~ m#^/#) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; @@ -583,7 +656,7 @@ X-Mailer: git-send-email $gitversion } else { print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n"; } - print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n"; + print $header, "\n"; if ($smtp) { print "Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; @@ -674,6 +747,7 @@ foreach my $t (@files) { if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) { my $c = $2; chomp $c; + next if ($c eq $sender and $suppress_from); push @cc, $c; printf("(sob) Adding cc: %s from line '%s'\n", $c, $_) unless $quiet; @@ -682,13 +756,14 @@ foreach my $t (@files) { } close F; - if ($cc_cmd ne "") { + if (defined $cc_cmd) { open(F, "$cc_cmd $t |") or die "(cc-cmd) Could not execute '$cc_cmd'"; while(<F>) { my $c = $_; $c =~ s/^\s*//g; $c =~ s/\n$//g; + next if ($c eq $sender and $suppress_from); push @cc, $c; printf("(cc-cmd) Adding cc: %s from: '%s'\n", $c, $cc_cmd) unless $quiet; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 3c325fd133..5aa62dda15 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -16,9 +16,40 @@ die() { exit 1 } -usage() { - die "Usage: $0 $USAGE" -} +if test -n "$OPTIONS_SPEC"; then + usage() { + exec "$0" -h + } + + parseopt_extra= + [ -n "$OPTIONS_KEEPDASHDASH" ] && + parseopt_extra="--keep-dashdash" + + eval "$( + echo "$OPTIONS_SPEC" | + git rev-parse --parseopt $parseopt_extra -- "$@" || + echo exit $? + )" +else + usage() { + die "Usage: $0 $USAGE" + } + + if [ -z "$LONG_USAGE" ] + then + LONG_USAGE="Usage: $0 $USAGE" + else + LONG_USAGE="Usage: $0 $USAGE + +$LONG_USAGE" + fi + + case "$1" in + -h|--h|--he|--hel|--help) + echo "$LONG_USAGE" + exit + esac +fi set_reflog_action() { if [ -z "${GIT_REFLOG_ACTION:+set}" ] @@ -91,26 +122,11 @@ get_author_ident_from_commit () { LANG=C LC_ALL=C sed -ne "$pick_author_script" } -if [ -z "$LONG_USAGE" ] -then - LONG_USAGE="Usage: $0 $USAGE" -else - LONG_USAGE="Usage: $0 $USAGE - -$LONG_USAGE" -fi - -case "$1" in - -h|--h|--he|--hel|--help) - echo "$LONG_USAGE" - exit -esac - # Make sure we are in a valid repository of a vintage we understand. if [ -z "$SUBDIRECTORY_OK" ] then : ${GIT_DIR=.git} - GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || { + test -z "$(git rev-parse --show-cdup)" || { exit=$? echo >&2 "You need to run this command from the toplevel of the working tree." exit $exit diff --git a/git-stash.sh b/git-stash.sh index 77c94210b7..b1529e28b1 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -4,6 +4,7 @@ USAGE='[ | list | show | apply | clear]' SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= . git-sh-setup require_work_tree cd_to_toplevel @@ -21,28 +22,22 @@ no_changes () { clear_stash () { if current=$(git rev-parse --verify $ref_stash 2>/dev/null) then - git update-ref -d refs/stash $current + git update-ref -d $ref_stash $current fi } -save_stash () { +create_stash () { stash_msg="$1" if no_changes then - echo >&2 'No local changes to save' exit 0 fi - test -f "$GIT_DIR/logs/$ref_stash" || - clear_stash || die "Cannot initialize stash" - - # Make sure the reflog for stash is kept. - : >>"$GIT_DIR/logs/$ref_stash" # state of the base commit if b_commit=$(git rev-parse --verify HEAD) then - head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD) + head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --) else die "You do not have the initial commit yet" fi @@ -84,6 +79,23 @@ save_stash () { w_commit=$(printf '%s\n' "$stash_msg" | git commit-tree $w_tree -p $b_commit -p $i_commit) || die "Cannot record working tree state" +} + +save_stash () { + stash_msg="$1" + + if no_changes + then + echo >&2 'No local changes to save' + exit 0 + fi + test -f "$GIT_DIR/logs/$ref_stash" || + clear_stash || die "Cannot initialize stash" + + create_stash "$stash_msg" + + # Make sure the reflog for stash is kept. + : >>"$GIT_DIR/logs/$ref_stash" git update-ref -m "$stash_msg" $ref_stash $w_commit || die "Cannot save the current status" @@ -96,7 +108,7 @@ have_stash () { list_stash () { have_stash || return 0 - git log --pretty=oneline -g "$@" $ref_stash | + git log --no-color --pretty=oneline -g "$@" $ref_stash -- | sed -n -e 's/^[.0-9a-f]* refs\///p' } @@ -202,6 +214,13 @@ apply) clear) clear_stash ;; +create) + if test $# -gt 0 && test "$1" = create + then + shift + fi + create_stash "$*" && echo "$w_commit" + ;; help | usage) usage ;; diff --git a/git-submodule.sh b/git-submodule.sh index b91d62632c..82ac28fa27 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,6 +5,7 @@ # Copyright (c) 2007 Lars Hjemli USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]' +OPTIONS_SPEC= . git-sh-setup require_work_tree @@ -39,6 +40,32 @@ get_repo_base() { ) 2>/dev/null } +# Resolve relative url by appending to parent's url +resolve_relative_url () +{ + branch="$(git symbolic-ref HEAD 2>/dev/null)" + remote="$(git config branch.${branch#refs/heads/}.remote)" + remote="${remote:-origin}" + remoteurl="$(git config remote.$remote.url)" || + die "remote ($remote) does not have a url in .git/config" + url="$1" + while test -n "$url" + do + case "$url" in + ../*) + url="${url#../}" + remoteurl="${remoteurl%/*}" + ;; + ./*) + url="${url#./}" + ;; + *) + break;; + esac + done + echo "$remoteurl/$url" +} + # # Map submodule path to submodule name # @@ -103,11 +130,19 @@ module_add() usage fi - # Turn the source into an absolute path if - # it is local - if base=$(get_repo_base "$repo"); then - repo="$base" - fi + case "$repo" in + ./*|../*) + # dereference source url relative to parent's url + realrepo="$(resolve_relative_url $repo)" ;; + *) + # Turn the source into an absolute path if + # it is local + if base=$(get_repo_base "$repo"); then + repo="$base" + fi + realrepo=$repo + ;; + esac # Guess path from repo if not specified or strip trailing slashes if test -z "$path"; then @@ -122,7 +157,7 @@ module_add() git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" - module_clone "$path" "$repo" || exit + module_clone "$path" "$realrepo" || exit (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || die "Unable to checkout submodule '$path'" git add "$path" || @@ -153,6 +188,13 @@ modules_init() test -z "$url" && die "No url found for submodule path '$path' in .gitmodules" + # Possibly a url relative to parent + case "$url" in + ./*|../*) + url="$(resolve_relative_url "$url")" + ;; + esac + git config submodule."$name".url "$url" || die "Failed to register url for submodule path '$path'" diff --git a/git-svn.perl b/git-svn.perl index 4c779b6c6d..43e1591cef 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,6 +9,11 @@ use vars qw/ $AUTHOR $VERSION $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; $VERSION = '@@GIT_VERSION@@'; +# From which subdir have we been invoked? +my $cmd_dir_prefix = eval { + command_oneline([qw/rev-parse --show-prefix/], STDERR => 0) +} || ''; + my $git_dir_user_set = 1 if defined $ENV{GIT_DIR}; $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; @@ -19,12 +24,12 @@ $Git::SVN::Log::TZ = $ENV{TZ}; $ENV{TZ} = 'UTC'; $| = 1; # unbuffer STDOUT -sub fatal (@) { print STDERR @_; exit 1 } +sub fatal (@) { print STDERR "@_\n"; exit 1 } require SVN::Core; # use()-ing this causes segfaults for me... *shrug* require SVN::Ra; require SVN::Delta; if ($SVN::Core::VERSION lt '1.1.0') { - fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n"; + fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; } push @Git::SVN::Ra::ISA, 'SVN::Ra'; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; @@ -43,7 +48,8 @@ BEGIN { foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher - Git::SVN::Migration Git::SVN::Log Git::SVN), + Git::SVN::Migration Git::SVN::Log Git::SVN + Git::SVN::Util), __PACKAGE__) { *{"${package}::$_"} = \&{"Git::$_"}; } @@ -59,7 +65,7 @@ my ($_stdin, $_help, $_edit, $_template, $_shared, $_version, $_fetch_all, $_no_rebase, $_merge, $_strategy, $_dry_run, $_local, - $_prefix, $_no_checkout, $_verbose); + $_prefix, $_no_checkout, $_url, $_verbose); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -123,8 +129,19 @@ my %cmd = ( 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], + 'create-ignore' => [ \&cmd_create_ignore, + 'Create a .gitignore per svn:ignore', + { 'revision|r=i' => \$_revision + } ], + 'propget' => [ \&cmd_propget, + 'Print the value of a property on a file or directory', + { 'revision|r=i' => \$_revision } ], + 'proplist' => [ \&cmd_proplist, + 'List all properties of a file or directory', + { 'revision|r=i' => \$_revision } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", - { 'revision|r=i' => \$_revision } ], + { 'revision|r=i' => \$_revision + } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -144,10 +161,10 @@ my %cmd = ( 'non-recursive' => \$Git::SVN::Log::non_recursive, 'authors-file|A=s' => \$_authors, 'color' => \$Git::SVN::Log::color, - 'pager=s' => \$Git::SVN::Log::pager, + 'pager=s' => \$Git::SVN::Log::pager } ], 'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish", - { } ], + {} ], 'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory", { 'merge|m|M' => \$_merge, 'verbose|v' => \$_verbose, @@ -161,6 +178,10 @@ my %cmd = ( 'file|F=s' => \$_file, 'revision|r=s' => \$_revision, %cmt_opts } ], + 'info' => [ \&cmd_info, + "Show info about the latest SVN revision + on the current branch", + { 'url' => \$_url, } ], ); my $cmd; @@ -236,7 +257,7 @@ Usage: $0 <command> [options] [arguments]\n next if $cmd && $cmd ne $_; next if /^multi-/; # don't show deprecated commands print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; - foreach (keys %{$cmd{$_}->[2]}) { + foreach (sort keys %{$cmd{$_}->[2]}) { # mixed-case options are for .git/config only next if /[A-Z]/ && /^[a-z]+$/i; # prints out arguments as they should be passed: @@ -356,7 +377,7 @@ sub cmd_set_tree { } elsif (scalar @tmp > 1) { push @revs, reverse(command('rev-list',@tmp)); } else { - fatal "Failed to rev-parse $c\n"; + fatal "Failed to rev-parse $c"; } } my $gs = Git::SVN->new; @@ -366,7 +387,7 @@ sub cmd_set_tree { fatal "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", "before committing.\nlast rev: $r_last\n", - " current: $gs->{last_rev}\n"; + " current: $gs->{last_rev}"; } $gs->set_tree($_) foreach @revs; print "Done committing ",scalar @revs," revisions to SVN\n"; @@ -375,7 +396,7 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) } - 'Cannot dcommit with a dirty index. Commit your changes first' + 'Cannot dcommit with a dirty index. Commit your changes first, ' . "or stash them with `git stash'.\n"; $head ||= 'HEAD'; my @refs; @@ -399,7 +420,7 @@ sub cmd_dcommit { (undef, $last_rev, undef) = cmt_metadata("$d~1"); unless (defined $last_rev) { fatal "Unable to extract revision information ", - "from commit $d~1\n"; + "from commit $d~1"; } } if ($_dry_run) { @@ -409,6 +430,9 @@ sub cmd_dcommit { my %ed_opts = ( r => $last_rev, log => get_commit_entry($d)->{log}, ra => Git::SVN::Ra->new($gs->full_url), + config => SVN::Core::config_get_config( + $Git::SVN::Ra::config_dir + ), tree_a => "$d~1", tree_b => $d, editor_cb => sub { @@ -530,7 +554,112 @@ sub cmd_show_ignore { my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); $gs ||= Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); - $gs->traverse_ignore(\*STDOUT, $gs->{path}, $r); + $gs->prop_walk($gs->{path}, $r, sub { + my ($gs, $path, $props) = @_; + print STDOUT "\n# $path\n"; + my $s = $props->{'svn:ignore'} or return; + $s =~ s/[\r\n]+/\n/g; + chomp $s; + $s =~ s#^#$path#gm; + print STDOUT "$s\n"; + }); +} + +sub cmd_create_ignore { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + $gs->prop_walk($gs->{path}, $r, sub { + my ($gs, $path, $props) = @_; + # $path is of the form /path/to/dir/ + my $ignore = '.' . $path . '.gitignore'; + my $s = $props->{'svn:ignore'} or return; + open(GITIGNORE, '>', $ignore) + or fatal("Failed to open `$ignore' for writing: $!"); + $s =~ s/[\r\n]+/\n/g; + chomp $s; + # Prefix all patterns so that the ignore doesn't apply + # to sub-directories. + $s =~ s#^#/#gm; + print GITIGNORE "$s\n"; + close(GITIGNORE) + or fatal("Failed to close `$ignore': $!"); + command_noisy('add', $ignore); + }); +} + +sub canonicalize_path { + my ($path) = @_; + my $dot_slash_added = 0; + if (substr($path, 0, 1) ne "/") { + $path = "./" . $path; + $dot_slash_added = 1; + } + # File::Spec->canonpath doesn't collapse x/../y into y (for a + # good reason), so let's do this manually. + $path =~ s#/+#/#g; + $path =~ s#/\.(?:/|$)#/#g; + $path =~ s#/[^/]+/\.\.##g; + $path =~ s#/$##g; + $path =~ s#^\./## if $dot_slash_added; + return $path; +} + +# get_svnprops(PATH) +# ------------------ +# Helper for cmd_propget and cmd_proplist below. +sub get_svnprops { + my $path = shift; + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + + # prefix THE PATH by the sub-directory from which the user + # invoked us. + $path = $cmd_dir_prefix . $path; + fatal("No such file or directory: $path") unless -e $path; + my $is_dir = -d $path ? 1 : 0; + $path = $gs->{path} . '/' . $path; + + # canonicalize the path (otherwise libsvn will abort or fail to + # find the file) + $path = canonicalize_path($path); + + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + my $props; + if ($is_dir) { + (undef, undef, $props) = $gs->ra->get_dir($path, $r); + } + else { + (undef, $props) = $gs->ra->get_file($path, $r, undef); + } + return $props; +} + +# cmd_propget (PROP, PATH) +# ------------------------ +# Print the SVN property PROP for PATH. +sub cmd_propget { + my ($prop, $path) = @_; + $path = '.' if not defined $path; + usage(1) if not defined $prop; + my $props = get_svnprops($path); + if (not defined $props->{$prop}) { + fatal("`$path' does not have a `$prop' SVN property."); + } + print $props->{$prop} . "\n"; +} + +# cmd_proplist (PATH) +# ------------------- +# Print the list of SVN properties for PATH. +sub cmd_proplist { + my $path = shift; + $path = '.' if not defined $path; + my $props = get_svnprops($path); + print "Properties on '$path':\n"; + foreach (sort keys %{$props}) { + print " $_\n"; + } } sub cmd_multi_init { @@ -579,7 +708,7 @@ sub cmd_multi_fetch { sub cmd_commit_diff { my ($ta, $tb, $url) = @_; my $usage = "Usage: $0 commit-diff -r<revision> ". - "<tree-ish> <tree-ish> [<URL>]\n"; + "<tree-ish> <tree-ish> [<URL>]"; fatal($usage) if (!defined $ta || !defined $tb); my $svn_path; if (!defined $url) { @@ -597,7 +726,7 @@ sub cmd_commit_diff { if (defined $_message && defined $_file) { fatal("Both --message/-m and --file/-F specified ", "for the commit message.\n", - "I have no idea what you mean\n"); + "I have no idea what you mean"); } if (defined $_file) { $_message = file_to_s($_file); @@ -624,6 +753,114 @@ sub cmd_commit_diff { } } +sub cmd_info { + my $path = canonicalize_path(shift or "."); + unless (scalar(@_) == 0) { + die "Too many arguments specified\n"; + } + + my ($file_type, $diff_status) = find_file_type_and_diff_status($path); + + if (!$file_type && !$diff_status) { + print STDERR "$path: (Not a versioned resource)\n\n"; + return; + } + + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + unless ($gs) { + die "Unable to determine upstream SVN information from ", + "working tree history\n"; + } + my $full_url = $url . ($path eq "." ? "" : "/$path"); + + if ($_url) { + print $full_url, "\n"; + return; + } + + my $result = "Path: $path\n"; + $result .= "Name: " . basename($path) . "\n" if $file_type ne "dir"; + $result .= "URL: " . $full_url . "\n"; + + eval { + my $repos_root = $gs->repos_root; + Git::SVN::remove_username($repos_root); + $result .= "Repository Root: $repos_root\n"; + }; + if ($@) { + $result .= "Repository Root: (offline)\n"; + } + $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A"; + $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n"; + + $result .= "Node Kind: " . + ($file_type eq "dir" ? "directory" : "file") . "\n"; + + my $schedule = $diff_status eq "A" + ? "add" + : ($diff_status eq "D" ? "delete" : "normal"); + $result .= "Schedule: $schedule\n"; + + if ($diff_status eq "A") { + print $result, "\n"; + return; + } + + my ($lc_author, $lc_rev, $lc_date_utc); + my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path); + my $log = command_output_pipe(@args); + my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; + while (<$log>) { + if (/^${esc_color}author (.+) <[^>]+> (\d+) ([\-\+]?\d+)$/o) { + $lc_author = $1; + $lc_date_utc = Git::SVN::Log::parse_git_date($2, $3); + } elsif (/^${esc_color} (git-svn-id:.+)$/o) { + (undef, $lc_rev, undef) = ::extract_metadata($1); + } + } + close $log; + + Git::SVN::Log::set_local_timezone(); + + $result .= "Last Changed Author: $lc_author\n"; + $result .= "Last Changed Rev: $lc_rev\n"; + $result .= "Last Changed Date: " . + Git::SVN::Log::format_svn_date($lc_date_utc) . "\n"; + + if ($file_type ne "dir") { + my $text_last_updated_date = + ($diff_status eq "D" ? $lc_date_utc : (stat $path)[9]); + $result .= + "Text Last Updated: " . + Git::SVN::Log::format_svn_date($text_last_updated_date) . + "\n"; + my $checksum; + if ($diff_status eq "D") { + my ($fh, $ctx) = + command_output_pipe(qw(cat-file blob), "HEAD:$path"); + if ($file_type eq "link") { + my $file_name = <$fh>; + $checksum = Git::SVN::Util::md5sum("link $file_name"); + } else { + $checksum = Git::SVN::Util::md5sum($fh); + } + command_close_pipe($fh, $ctx); + } elsif ($file_type eq "link") { + my $file_name = + command(qw(cat-file blob), "HEAD:$path"); + $checksum = + Git::SVN::Util::md5sum("link " . $file_name); + } else { + open FILE, "<", $path or die $!; + $checksum = Git::SVN::Util::md5sum(\*FILE); + close FILE or die $!; + } + $result .= "Checksum: " . $checksum . "\n"; + } + + print $result, "\n"; +} + ########################### utility functions ######################### sub rebase_cmd { @@ -660,7 +897,7 @@ sub complete_svn_url { if ($path !~ m#^[a-z\+]+://#) { if (!defined $url || $url !~ m#^[a-z\+]+://#) { fatal("E: '$path' is not a complete URL ", - "and a separate URL is not specified\n"); + "and a separate URL is not specified"); } return ($url, $path); } @@ -681,7 +918,7 @@ sub complete_url_ls_init { $repo_path =~ s#^/+##; unless ($ra) { fatal("E: '$repo_path' is not a complete URL ", - "and a separate URL is not specified\n"); + "and a separate URL is not specified"); } } my $url = $ra->{url}; @@ -854,7 +1091,8 @@ sub cmt_metadata { sub working_head_info { my ($head, $refs) = @_; - my ($fh, $ctx) = command_output_pipe('log', '--no-color', $head); + my @args = ('log', '--no-color', '--first-parent'); + my ($fh, $ctx) = command_output_pipe(@args, $head); my $hash; my %max; while (<$fh>) { @@ -930,6 +1168,52 @@ sub linearize_history { (\@linear_refs, \%parents); } +sub find_file_type_and_diff_status { + my ($path) = @_; + return ('dir', '') if $path eq '.'; + + my $diff_output = + command_oneline(qw(diff --cached --name-status --), $path) || ""; + my $diff_status = (split(' ', $diff_output))[0] || ""; + + my $ls_tree = command_oneline(qw(ls-tree HEAD), $path) || ""; + + return (undef, undef) if !$diff_status && !$ls_tree; + + if ($diff_status eq "A") { + return ("link", $diff_status) if -l $path; + return ("dir", $diff_status) if -d $path; + return ("file", $diff_status); + } + + my $mode = (split(' ', $ls_tree))[0] || ""; + + return ("link", $diff_status) if $mode eq "120000"; + return ("dir", $diff_status) if $mode eq "040000"; + return ("file", $diff_status); +} + +package Git::SVN::Util; +use strict; +use warnings; +use Digest::MD5; + +sub md5sum { + my $arg = shift; + my $ref = ref $arg; + my $md5 = Digest::MD5->new(); + if ($ref eq 'GLOB' || $ref eq 'IO::File') { + $md5->addfile($arg) or croak $!; + } elsif ($ref eq 'SCALAR') { + $md5->add($$arg) or croak $!; + } elsif (!$ref) { + $md5->add($arg) or croak $!; + } else { + ::fatal "Can't provide MD5 hash for unknown ref type: '", $ref, "'"; + } + return $md5->hexdigest(); +} + package Git::SVN; use strict; use warnings; @@ -1494,9 +1778,24 @@ sub ra_uuid { $self->{ra_uuid}; } +sub _set_repos_root { + my ($self, $repos_root) = @_; + my $k = "svn-remote.$self->{repo_id}.reposRoot"; + $repos_root ||= $self->ra->{repos_root}; + tmp_config($k, $repos_root); + $repos_root; +} + +sub repos_root { + my ($self) = @_; + my $k = "svn-remote.$self->{repo_id}.reposRoot"; + eval { tmp_config('--get', $k) } || $self->_set_repos_root; +} + sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); + $self->_set_repos_root($ra->{repos_root}); if ($self->use_svm_props && !$self->{svm}) { if ($self->no_metadata) { die "Can't have both 'noMetadata' and ", @@ -1521,28 +1820,45 @@ sub rel_path { $url; } -sub traverse_ignore { - my ($self, $fh, $path, $r) = @_; - $path =~ s#^/+##g; - my $ra = $self->ra; - my ($dirent, undef, $props) = $ra->get_dir($path, $r); +# prop_walk(PATH, REV, SUB) +# ------------------------- +# Recursively traverse PATH at revision REV and invoke SUB for each +# directory that contains a SVN property. SUB will be invoked as +# follows: &SUB(gs, path, props); where `gs' is this instance of +# Git::SVN, `path' the path to the directory where the properties +# `props' were found. The `path' will be relative to point of checkout, +# that is, if url://repo/trunk is the current Git branch, and that +# directory contains a sub-directory `d', SUB will be invoked with `/d/' +# as `path' (note the trailing `/'). +sub prop_walk { + my ($self, $path, $rev, $sub) = @_; + + my ($dirent, undef, $props) = $self->ra->get_dir($path, $rev); + $path =~ s#^/*#/#g; my $p = $path; - $p =~ s#^\Q$self->{path}\E(/|$)##; - print $fh length $p ? "\n# $p\n" : "\n# /\n"; - if (my $s = $props->{'svn:ignore'}) { - $s =~ s/[\r\n]+/\n/g; - chomp $s; - if (length $p == 0) { - $s =~ s#\n#\n/$p#g; - print $fh "/$s\n"; - } else { - $s =~ s#\n#\n/$p/#g; - print $fh "/$p/$s\n"; - } - } + # Strip the irrelevant part of the path. + $p =~ s#^/+\Q$self->{path}\E(/|$)#/#; + # Ensure the path is terminated by a `/'. + $p =~ s#/*$#/#; + + # The properties contain all the internal SVN stuff nobody + # (usually) cares about. + my $interesting_props = 0; + foreach (keys %{$props}) { + # If it doesn't start with `svn:', it must be a + # user-defined property. + ++$interesting_props and next if $_ !~ /^svn:/; + # FIXME: Fragile, if SVN adds new public properties, + # this needs to be updated. + ++$interesting_props if /^svn:(?:ignore|keywords|executable + |eol-style|mime-type + |externals|needs-lock)$/x; + } + &$sub($self, $p, $props) if $interesting_props; + foreach (sort keys %$dirent) { next if $dirent->{$_}->{kind} != $SVN::Node::dir; - $self->traverse_ignore($fh, "$path/$_", $r); + $self->prop_walk($path . '/' . $_, $rev, $sub); } } @@ -1669,7 +1985,7 @@ sub assert_index_clean { $x = command_oneline('write-tree'); if ($y ne $x) { ::fatal "trees ($treeish) $y != $x\n", - "Something is seriously wrong...\n"; + "Something is seriously wrong..."; } }); } @@ -1888,6 +2204,16 @@ sub find_parent_branch { $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; + } elsif ($self->ra->trees_match($new_url, $r0, + $self->full_url, $rev)) { + print STDERR "Trees match:\n", + " $new_url\@$r0\n", + " ${\$self->full_url}\@$rev\n", + "Following parent with no changes\n"; + $self->tmp_index_do(sub { + command_noisy('read-tree', $parent); + }); + $self->{last_commit} = $parent; } else { print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); @@ -2085,7 +2411,7 @@ sub set_tree { my ($self, $tree) = (shift, shift); my $log_entry = ::get_commit_entry($tree); unless ($self->{last_rev}) { - fatal("Must have an existing revision to commit\n"); + fatal("Must have an existing revision to commit"); } my %ed_opts = ( r => $self->{last_rev}, log => $log_entry->{log}, @@ -2259,10 +2585,15 @@ sub rev_db_get { $ret; } +# Finds the first svn revision that exists on (if $eq_ok is true) or +# before $rev for the current branch. It will not search any lower +# than $min_rev. Returns the git commit hash and svn revision number +# if found, else (undef, undef). sub find_rev_before { - my ($self, $rev, $eq_ok) = @_; + my ($self, $rev, $eq_ok, $min_rev) = @_; --$rev unless $eq_ok; - while ($rev > 0) { + $min_rev ||= 1; + while ($rev >= $min_rev) { if (my $c = $self->rev_db_get($rev)) { return ($rev, $c); } @@ -2271,6 +2602,23 @@ sub find_rev_before { return (undef, undef); } +# Finds the first svn revision that exists on (if $eq_ok is true) or +# after $rev for the current branch. It will not search any higher +# than $max_rev. Returns the git commit hash and svn revision number +# if found, else (undef, undef). +sub find_rev_after { + my ($self, $rev, $eq_ok, $max_rev) = @_; + ++$rev unless $eq_ok; + $max_rev ||= $self->rev_db_max(); + while ($rev <= $max_rev) { + if (my $c = $self->rev_db_get($rev)) { + return ($rev, $c); + } + ++$rev; + } + return (undef, undef); +} + sub _new { my ($class, $repo_id, $ref_id, $path) = @_; unless (defined $repo_id && length $repo_id) { @@ -2334,23 +2682,31 @@ sub ssl_server_trust { my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Error validating server certificate for '$realm':\n"; - if ($failures & $SVN::Auth::SSL::UNKNOWNCA) { - print STDERR " - The certificate is not issued by a trusted ", - "authority. Use the\n", - " fingerprint to validate the certificate manually!\n"; - } - if ($failures & $SVN::Auth::SSL::CNMISMATCH) { - print STDERR " - The certificate hostname does not match.\n"; - } - if ($failures & $SVN::Auth::SSL::NOTYETVALID) { - print STDERR " - The certificate is not yet valid.\n"; - } - if ($failures & $SVN::Auth::SSL::EXPIRED) { - print STDERR " - The certificate has expired.\n"; - } - if ($failures & $SVN::Auth::SSL::OTHER) { - print STDERR " - The certificate has an unknown error.\n"; - } + { + no warnings 'once'; + # All variables SVN::Auth::SSL::* are used only once, + # so we're shutting up Perl warnings about this. + if ($failures & $SVN::Auth::SSL::UNKNOWNCA) { + print STDERR " - The certificate is not issued ", + "by a trusted authority. Use the\n", + " fingerprint to validate ", + "the certificate manually!\n"; + } + if ($failures & $SVN::Auth::SSL::CNMISMATCH) { + print STDERR " - The certificate hostname ", + "does not match.\n"; + } + if ($failures & $SVN::Auth::SSL::NOTYETVALID) { + print STDERR " - The certificate is not yet valid.\n"; + } + if ($failures & $SVN::Auth::SSL::EXPIRED) { + print STDERR " - The certificate has expired.\n"; + } + if ($failures & $SVN::Auth::SSL::OTHER) { + print STDERR " - The certificate has ", + "an unknown error.\n"; + } + } # no warnings 'once' printf STDERR "Certificate information:\n". " - Hostname: %s\n". @@ -2434,27 +2790,12 @@ sub _read_password { $password; } -package main; - -{ - my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Auth::SSL::CNMISMATCH. - $SVN::Auth::SSL::NOTYETVALID. - $SVN::Auth::SSL::EXPIRED. - $SVN::Auth::SSL::UNKNOWNCA. - $SVN::Auth::SSL::OTHER; -} - package SVN::Git::Fetcher; use vars qw/@ISA/; use strict; use warnings; use Carp qw/croak/; use IO::File qw//; -use Digest::MD5; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -2606,9 +2947,7 @@ sub apply_textdelta { if (defined $exp) { seek $base, 0, 0 or croak $!; - my $md5 = Digest::MD5->new; - $md5->addfile($base); - my $got = $md5->hexdigest; + my $got = Git::SVN::Util::md5sum($base); die "Checksum mismatch: $fb->{path} $fb->{blob}\n", "expected: $exp\n", " got: $got\n" if ($got ne $exp); @@ -2627,9 +2966,7 @@ sub close_file { if (my $fh = $fb->{fh}) { if (defined $exp) { seek($fh, 0, 0) or croak $!; - my $md5 = Digest::MD5->new; - $md5->addfile($fh); - my $got = $md5->hexdigest; + my $got = Git::SVN::Util::md5sum($fh); if ($got ne $exp) { die "Checksum mismatch: $path\n", "expected: $exp\n got: $got\n"; @@ -2681,7 +3018,6 @@ use strict; use warnings; use Carp qw/croak/; use IO::File; -use Digest::MD5; sub new { my ($class, $opts) = @_; @@ -2864,16 +3200,21 @@ sub open_or_add_dir { if (!defined $t) { die "$full_path not known in r$self->{r} or we have a bug!\n"; } - if ($t == $SVN::Node::none) { - return $self->add_directory($full_path, $baton, - undef, -1, $self->{pool}); - } elsif ($t == $SVN::Node::dir) { - return $self->open_directory($full_path, $baton, - $self->{r}, $self->{pool}); - } - print STDERR "$full_path already exists in repository at ", - "r$self->{r} and it is not a directory (", - ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n"; + { + no warnings 'once'; + # SVN::Node::none and SVN::Node::file are used only once, + # so we're shutting up Perl's warnings about them. + if ($t == $SVN::Node::none) { + return $self->add_directory($full_path, $baton, + undef, -1, $self->{pool}); + } elsif ($t == $SVN::Node::dir) { + return $self->open_directory($full_path, $baton, + $self->{r}, $self->{pool}); + } # no warnings 'once' + print STDERR "$full_path already exists in repository at ", + "r$self->{r} and it is not a directory (", + ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n"; + } # no warnings 'once' exit 1; } @@ -2980,11 +3321,9 @@ sub chg_file { $fh->flush == 0 or croak $!; seek $fh, 0, 0 or croak $!; - my $md5 = Digest::MD5->new; - $md5->addfile($fh) or croak $!; + my $exp = Git::SVN::Util::md5sum($fh); seek $fh, 0, 0 or croak $!; - my $exp = $md5->hexdigest; my $pool = SVN::Pool->new; my $atd = $self->apply_textdelta($fbat, undef, $pool); my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool); @@ -3035,7 +3374,7 @@ sub apply_diff { if (defined $o{$f}) { $self->$f($m); } else { - fatal("Invalid change type: $f\n"); + fatal("Invalid change type: $f"); } } $self->rmdirs if $_rmdir; @@ -3068,34 +3407,81 @@ BEGIN { } } +sub _auth_providers () { + [ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&Git::SVN::Prompt::simple, 2), + SVN::Client::get_ssl_client_cert_file_provider(), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert_pw, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&Git::SVN::Prompt::ssl_server_trust), + SVN::Client::get_username_prompt_provider( + \&Git::SVN::Prompt::username, 2) + ] +} + +sub escape_uri_only { + my ($uri) = @_; + my @tmp; + foreach (split m{/}, $uri) { + s/([^\w.-])/sprintf("%%%02X",ord($1))/eg; + push @tmp, $_; + } + join('/', @tmp); +} + +sub escape_url { + my ($url) = @_; + if ($url =~ m#^(https?)://([^/]+)(.*)$#) { + my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3)); + $url = "$scheme://$domain$uri"; + } + $url; +} + sub new { my ($class, $url) = @_; $url =~ s!/+$!!; return $RA if ($RA && $RA->{url} eq $url); SVN::_Core::svn_config_ensure($config_dir, undef); - my ($baton, $callbacks) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&Git::SVN::Prompt::simple, 2), - SVN::Client::get_ssl_client_cert_file_provider(), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert_pw, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&Git::SVN::Prompt::ssl_server_trust), - SVN::Client::get_username_prompt_provider( - \&Git::SVN::Prompt::username, 2), - ]); + my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers); my $config = SVN::Core::config_get_config($config_dir); $RA = undef; - my $self = SVN::Ra->new(url => $url, auth => $baton, + my $dont_store_passwords = 1; + my $conf_t = ${$config}{'config'}; + { + no warnings 'once'; + # The usage of $SVN::_Core::SVN_CONFIG_* variables + # produces warnings that variables are used only once. + # I had not found the better way to shut them up, so + # the warnings of type 'once' are disabled in this block. + if (SVN::_Core::svn_config_get_bool($conf_t, + $SVN::_Core::SVN_CONFIG_SECTION_AUTH, + $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS, + 1) == 0) { + SVN::_Core::svn_auth_set_parameter($baton, + $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, + bless (\$dont_store_passwords, "_p_void")); + } + if (SVN::_Core::svn_config_get_bool($conf_t, + $SVN::_Core::SVN_CONFIG_SECTION_AUTH, + $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + 1) == 0) { + $Git::SVN::Prompt::_no_auth_cache = 1; + } + } # no warnings 'once' + my $self = SVN::Ra->new(url => escape_url($url), auth => $baton, config => $config, pool => SVN::Pool->new, auth_provider_callbacks => $callbacks); + $self->{url} = $url; $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; $self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##; @@ -3153,6 +3539,24 @@ sub get_log { $ret; } +sub trees_match { + my ($self, $url1, $rev1, $url2, $rev2) = @_; + my $ctx = SVN::Client->new(auth => _auth_providers); + my $out = IO::File->new_tmpfile; + + # older SVN (1.1.x) doesn't take $pool as the last parameter for + # $ctx->diff(), so we'll create a default one + my $pool = SVN::Pool->new_default_sub; + + $ra_invalid = 1; # this will open a new SVN::Ra connection to $url1 + $ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out); + $out->flush; + my $ret = (($out->stat)[7] == 0); + close $out or croak $!; + + $ret; +} + sub get_commit_editor { my ($self, $log, $cb, $pool) = @_; my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); @@ -3203,7 +3607,7 @@ sub gs_do_switch { my $full_url = $self->{url}; my $old_url = $full_url; - $full_url .= "/$path" if length $path; + $full_url .= '/' . escape_uri_only($path) if length $path; my ($ra, $reparented); if ($old_url ne $full_url) { if ($old_url !~ m#^svn(\+ssh)?://#) { @@ -3497,6 +3901,7 @@ package Git::SVN::Log; use strict; use warnings; use POSIX qw/strftime/; +use constant commit_log_separator => ('-' x 72) . "\n"; use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline %rusers $show_commit $incremental/; my $l_fmt; @@ -3590,19 +3995,19 @@ sub git_svn_log_cmd { push @cmd, $c; } } elsif (defined $r_max) { - my ($c_min, $c_max); - $c_max = $gs->rev_db_get($r_max); - $c_min = $gs->rev_db_get($r_min); - if (defined $c_min && defined $c_max) { - if ($r_max > $r_max) { - push @cmd, "$c_min..$c_max"; - } else { - push @cmd, "$c_max..$c_min"; - } - } elsif ($r_max > $r_min) { - push @cmd, $c_max; + if ($r_max < $r_min) { + ($r_min, $r_max) = ($r_max, $r_min); + } + my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min); + my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max); + # If there are no commits in the range, both $c_max and $c_min + # will be undefined. If there is at least 1 commit in the + # range, both will be defined. + return () if !defined $c_min || !defined $c_max; + if ($c_min eq $c_max) { + push @cmd, '--max-count=1', $c_min; } else { - push @cmd, $c_min; + push @cmd, '--boundary', "$c_min..$c_max"; } } return (@cmd, @files); @@ -3621,15 +4026,38 @@ sub config_pager { sub run_pager { return unless -t *STDOUT && defined $pager; pipe my $rfd, my $wfd or return; - defined(my $pid = fork) or ::fatal "Can't fork: $!\n"; + defined(my $pid = fork) or ::fatal "Can't fork: $!"; if (!$pid) { open STDOUT, '>&', $wfd or - ::fatal "Can't redirect to stdout: $!\n"; + ::fatal "Can't redirect to stdout: $!"; return; } - open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n"; + open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!"; $ENV{LESS} ||= 'FRSX'; - exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; + exec $pager or ::fatal "Can't run pager: $! ($pager)"; +} + +sub format_svn_date { + return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift)); +} + +sub parse_git_date { + my ($t, $tz) = @_; + # Date::Parse isn't in the standard Perl distro :( + if ($tz =~ s/^\+//) { + $t += tz_to_s_offset($tz); + } elsif ($tz =~ s/^\-//) { + $t -= tz_to_s_offset($tz); + } + return $t; +} + +sub set_local_timezone { + if (defined $TZ) { + $ENV{TZ} = $TZ; + } else { + delete $ENV{TZ}; + } } sub tz_to_s_offset { @@ -3652,13 +4080,7 @@ sub get_author_info { $dest->{t} = $t; $dest->{tz} = $tz; $dest->{a} = $au; - # Date::Parse isn't in the standard Perl distro :( - if ($tz =~ s/^\+//) { - $t += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $t -= tz_to_s_offset($tz); - } - $dest->{t_utc} = $t; + $dest->{t_utc} = parse_git_date($t, $tz); } sub process_commit { @@ -3710,10 +4132,9 @@ sub show_commit_changed_paths { sub show_commit_normal { my ($c) = @_; - print '-' x72, "\nr$c->{r} | "; + print commit_log_separator, "r$c->{r} | "; print "$c->{c} | " if $show_commit; - print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", - localtime($c->{t_utc})), ' | '; + print "$c->{a} | ", format_svn_date($c->{t_utc}), ' | '; my $nr_line = 0; if (my $l = $c->{l}) { @@ -3753,11 +4174,7 @@ sub cmd_show_log { my (@args) = @_; my ($r_min, $r_max); my $r_last = -1; # prevent dupes - if (defined $TZ) { - $ENV{TZ} = $TZ; - } else { - delete $ENV{TZ}; - } + set_local_timezone(); if (defined $::_revision) { if ($::_revision =~ /^(\d+):(\d+)$/) { ($r_min, $r_max) = ($1, $2); @@ -3765,18 +4182,22 @@ sub cmd_show_log { $r_min = $r_max = $::_revision; } else { ::fatal "-r$::_revision is not supported, use ", - "standard \'git log\' arguments instead\n"; + "standard 'git log' arguments instead"; } } config_pager(); @args = git_svn_log_cmd($r_min, $r_max, @args); + if (!@args) { + print commit_log_separator unless $incremental || $oneline; + return; + } my $log = command_output_pipe(@args); run_pager(); my (@k, $c, $d, $stat); my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; while (<$log>) { - if (/^${esc_color}commit ($::sha1_short)/o) { + if (/^${esc_color}commit -?($::sha1_short)/o) { my $cmt = $1; if ($c && cmt_showable($c) && $c->{r} != $r_last) { $r_last = $c->{r}; @@ -3819,14 +4240,12 @@ sub cmd_show_log { process_commit($c, $r_min, $r_max, \@k); } if (@k) { - my $swap = $r_max; - $r_max = $r_min; - $r_min = $swap; + ($r_min, $r_max) = ($r_max, $r_min); process_commit($_, $r_min, $r_max) foreach reverse @k; } out: close $log; - print '-' x72,"\n" unless $incremental || $oneline; + print commit_log_separator unless $incremental || $oneline; } package Git::SVN::Migration; @@ -6,28 +6,6 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; -static void prepend_to_path(const char *dir, int len) -{ - const char *old_path = getenv("PATH"); - char *path; - int path_len = len; - - if (!old_path) - old_path = "/usr/local/bin:/usr/bin:/bin"; - - path_len = len + strlen(old_path) + 1; - - path = xmalloc(path_len + 1); - - memcpy(path, dir, len); - path[len] = ':'; - memcpy(path + len + 1, old_path, path_len - len); - - setenv("PATH", path, 1); - - free(path); -} - static int handle_options(const char*** argv, int* argc, int* envchanged) { int handled = 0; @@ -51,7 +29,7 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) if (!prefixcmp(cmd, "--exec-path")) { cmd += 11; if (*cmd == '=') - git_set_exec_path(cmd + 1); + git_set_argv_exec_path(cmd + 1); else { puts(git_exec_path()); exit(0); @@ -187,19 +165,13 @@ static int handle_alias(int *argcp, const char ***argv) if (alias_string) { if (alias_string[0] == '!') { if (*argcp > 1) { - int i, sz = PATH_MAX; - char *s = xmalloc(sz), *new_alias = s; + struct strbuf buf; - add_to_string(&s, &sz, alias_string, 0); + strbuf_init(&buf, PATH_MAX); + strbuf_addstr(&buf, alias_string); + sq_quote_argv(&buf, (*argv) + 1, *argcp - 1, PATH_MAX); free(alias_string); - alias_string = new_alias; - for (i = 1; i < *argcp && - !add_to_string(&s, &sz, " ", 0) && - !add_to_string(&s, &sz, (*argv)[i], 1) - ; i++) - ; /* do nothing */ - if (!sz) - die("Too many or long arguments"); + alias_string = buf.buf; } trace_printf("trace: alias to shell cmd: %s => %s\n", alias_command, alias_string + 1); @@ -277,19 +249,14 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if (p->option & NEED_WORK_TREE) { - const char *work_tree = get_git_work_tree(); - const char *git_dir = get_git_dir(); - if (!is_absolute_path(git_dir)) - set_git_dir(make_absolute_path(git_dir)); - if (!work_tree || chdir(work_tree)) - die("%s must be run in a work tree", p->cmd); - } + if (p->option & NEED_WORK_TREE) + setup_work_tree(); + trace_argv_printf(argv, argc, "trace: built-in: git"); status = p->fn(argc, argv, prefix); if (status) - return status; + return status & 0xff; /* Somebody closed stdout? */ if (fstat(fileno(stdout), &st)) @@ -326,6 +293,7 @@ static void handle_internal_command(int argc, const char **argv) { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, + { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP }, @@ -334,6 +302,8 @@ static void handle_internal_command(int argc, const char **argv) { "diff-files", cmd_diff_files }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fetch", cmd_fetch, RUN_SETUP }, + { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, @@ -344,18 +314,24 @@ static void handle_internal_command(int argc, const char **argv) { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, { "help", cmd_help }, +#ifndef NO_CURL + { "http-fetch", cmd_http_fetch, RUN_SETUP }, +#endif { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, + { "ls-remote", cmd_ls_remote }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, + { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "peek-remote", cmd_ls_remote }, { "pickaxe", cmd_blame, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, @@ -364,11 +340,13 @@ static void handle_internal_command(int argc, const char **argv) { "reflog", cmd_reflog, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, + { "reset", cmd_reset, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, - { "rev-parse", cmd_rev_parse, RUN_SETUP }, + { "rev-parse", cmd_rev_parse }, { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, - { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE }, + { "rm", cmd_rm, RUN_SETUP }, { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE }, + { "send-pack", cmd_send_pack, RUN_SETUP }, { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, @@ -408,19 +386,17 @@ int main(int argc, const char **argv) { const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); - const char *exec_path = NULL; + const char *cmd_path = NULL; int done_alias = 0; /* * Take the basename of argv[0] as the command * name, and the dirname as the default exec_path - * if it's an absolute path and we don't have - * anything better. + * if we don't have anything better. */ if (slash) { *slash++ = 0; - if (*cmd == '/') - exec_path = cmd; + cmd_path = cmd; cmd = slash; } @@ -449,23 +425,20 @@ int main(int argc, const char **argv) if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { - /* Default command: "help" */ - argv[0] = "help"; - argc = 1; + /* The user didn't specify a command; give them help */ + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + exit(1); } cmd = argv[0]; /* - * We execute external git command via execv_git_cmd(), - * which looks at "--exec-path" option, GIT_EXEC_PATH - * environment, and $(gitexecdir) in Makefile while built, - * in this order. For scripted commands, we prepend - * the value of the exec_path variable to the PATH. + * We use PATH to find git commands, but we prepend some higher + * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH + * environment, and the $(gitexecdir) from the Makefile at build + * time. */ - if (exec_path) - prepend_to_path(exec_path, strlen(exec_path)); - exec_path = git_exec_path(); - prepend_to_path(exec_path, strlen(exec_path)); + setup_path(cmd_path); while (1) { /* See if it's an internal command */ @@ -82,17 +82,20 @@ proc dorunq {} { proc start_rev_list {view} { global startmsecs global commfd leftover tclencoding datemode - global viewargs viewfiles commitidx - global lookingforhead showlocalchanges + global viewargs viewfiles commitidx viewcomplete vnextroot + global showlocalchanges commitinterest mainheadid + global progressdirn progresscoords proglastnc curview set startmsecs [clock clicks -milliseconds] set commitidx($view) 0 + set viewcomplete($view) 0 + set vnextroot($view) 0 set order "--topo-order" if {$datemode} { set order "--date-order" } if {[catch { - set fd [open [concat | git log -z --pretty=raw $order --parents \ + set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \ --boundary $viewargs($view) "--" $viewfiles($view)] r] } err]} { error_popup "Error executing git rev-list: $err" @@ -100,13 +103,20 @@ proc start_rev_list {view} { } set commfd($view) $fd set leftover($view) {} - set lookingforhead $showlocalchanges + if {$showlocalchanges} { + lappend commitinterest($mainheadid) {dodiffindex} + } fconfigure $fd -blocking 0 -translation lf -eofchar {} if {$tclencoding != {}} { fconfigure $fd -encoding $tclencoding } filerun $fd [list getcommitlines $fd $view] - nowbusy $view + nowbusy $view "Reading" + if {$view == $curview} { + set progressdirn 1 + set progresscoords {0 0} + set proglastnc 0 + } } proc stop_rev_list {} { @@ -123,7 +133,7 @@ proc stop_rev_list {} { } proc getcommits {} { - global phase canv mainfont curview + global phase canv curview set phase getcommits initlayout @@ -131,12 +141,26 @@ proc getcommits {} { show_status "Reading commits..." } +# This makes a string representation of a positive integer which +# sorts as a string in numerical order +proc strrep {n} { + if {$n < 16} { + return [format "%x" $n] + } elseif {$n < 256} { + return [format "x%.2x" $n] + } elseif {$n < 65536} { + return [format "y%.4x" $n] + } + return [format "z%.8x" $n] +} + proc getcommitlines {fd view} { - global commitlisted + global commitlisted commitinterest global leftover commfd - global displayorder commitidx commitrow commitdata + global displayorder commitidx viewcomplete commitrow commitdata global parentlist children curview hlview global vparentlist vdisporder vcmitlisted + global ordertok vnextroot idpending set stuff [read $fd 500000] # git log doesn't terminate the last commit with a null... @@ -147,9 +171,29 @@ proc getcommitlines {fd view} { if {![eof $fd]} { return 1 } - global viewname + # Check if we have seen any ids listed as parents that haven't + # appeared in the list + foreach vid [array names idpending "$view,*"] { + # should only get here if git log is buggy + set id [lindex [split $vid ","] 1] + set commitrow($vid) $commitidx($view) + incr commitidx($view) + if {$view == $curview} { + lappend parentlist {} + lappend displayorder $id + lappend commitlisted 0 + } else { + lappend vparentlist($view) {} + lappend vdisporder($view) $id + lappend vcmitlisted($view) 0 + } + } + set viewcomplete($view) 1 + global viewname progresscoords unset commfd($view) notbusy $view + set progresscoords {0 0} + adjustprogress # set it blocking so we wait for the process to terminate fconfigure $fd -blocking 1 if {[catch {close $fd} err]} { @@ -221,14 +265,35 @@ proc getcommitlines {fd view} { exit 1 } set id [lindex $ids 0] + if {![info exists ordertok($view,$id)]} { + set otok "o[strrep $vnextroot($view)]" + incr vnextroot($view) + set ordertok($view,$id) $otok + } else { + set otok $ordertok($view,$id) + unset idpending($view,$id) + } if {$listed} { set olds [lrange $ids 1 end] - set i 0 - foreach p $olds { - if {$i == 0 || [lsearch -exact $olds $p] >= $i} { - lappend children($view,$p) $id + if {[llength $olds] == 1} { + set p [lindex $olds 0] + lappend children($view,$p) $id + if {![info exists ordertok($view,$p)]} { + set ordertok($view,$p) $ordertok($view,$id) + set idpending($view,$p) 1 + } + } else { + set i 0 + foreach p $olds { + if {$i == 0 || [lsearch -exact $olds $p] >= $i} { + lappend children($view,$p) $id + } + if {![info exists ordertok($view,$p)]} { + set ordertok($view,$p) "$otok[strrep $i]]" + set idpending($view,$p) 1 + } + incr i } - incr i } } else { set olds {} @@ -248,24 +313,54 @@ proc getcommitlines {fd view} { lappend vdisporder($view) $id lappend vcmitlisted($view) $listed } + if {[info exists commitinterest($id)]} { + foreach script $commitinterest($id) { + eval [string map [list "%I" $id] $script] + } + unset commitinterest($id) + } set gotsome 1 } if {$gotsome} { run chewcommits $view + if {$view == $curview} { + # update progress bar + global progressdirn progresscoords proglastnc + set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}] + set proglastnc $commitidx($view) + set l [lindex $progresscoords 0] + set r [lindex $progresscoords 1] + if {$progressdirn} { + set r [expr {$r + $inc}] + if {$r >= 1.0} { + set r 1.0 + set progressdirn 0 + } + if {$r > 0.2} { + set l [expr {$r - 0.2}] + } + } else { + set l [expr {$l - $inc}] + if {$l <= 0.0} { + set l 0.0 + set progressdirn 1 + } + set r [expr {$l + 0.2}] + } + set progresscoords [list $l $r] + adjustprogress + } } return 2 } proc chewcommits {view} { - global curview hlview commfd + global curview hlview viewcomplete global selectedline pending_select - set more 0 if {$view == $curview} { - set allread [expr {![info exists commfd($view)]}] - set tlimit [expr {[clock clicks -milliseconds] + 50}] - set more [layoutmore $tlimit $allread] - if {$allread && !$more} { + layoutmore + if {$viewcomplete($view)} { global displayorder commitidx phase global numcommits startmsecs @@ -286,7 +381,7 @@ proc chewcommits {view} { if {[info exists hlview] && $view == $hlview} { vhighlightmore } - return $more + return 0 } proc readcommit {id} { @@ -295,7 +390,7 @@ proc readcommit {id} { } proc updatecommits {} { - global viewdata curview phase displayorder + global viewdata curview phase displayorder ordertok idpending global children commitrow selectedline thickerline showneartags if {$phase ne {}} { @@ -306,6 +401,10 @@ proc updatecommits {} { foreach id $displayorder { catch {unset children($n,$id)} catch {unset commitrow($n,$id)} + catch {unset ordertok($n,$id)} + } + foreach vid [array names idpending "$n,*"] { + unset idpending($vid) } set curview -1 catch {unset selectedline} @@ -516,7 +615,7 @@ proc confirm_popup msg { proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist - global textfont mainfont uifont tabstop + global tabstop global findtype findtypemenu findloc findstring fstring geometry global entries sha1entry sha1string sha1but global diffcontextstring diffcontext @@ -525,23 +624,26 @@ proc makewindow {} { global highlight_files gdttype global searchstring sstring global bgcolor fgcolor bglist fglist diffcolors selectbgcolor - global headctxmenu + global headctxmenu progresscanv progressitem progresscoords statusw + global fprogitem fprogcoord lastprogupdate progupdatepending + global rprogitem rprogcoord + global have_tk85 menu .bar .bar add cascade -label "File" -menu .bar.file - .bar configure -font $uifont + .bar configure -font uifont menu .bar.file .bar.file add command -label "Update" -command updatecommits .bar.file add command -label "Reread references" -command rereadrefs .bar.file add command -label "List references" -command showrefs .bar.file add command -label "Quit" -command doquit - .bar.file configure -font $uifont + .bar.file configure -font uifont menu .bar.edit .bar add cascade -label "Edit" -menu .bar.edit .bar.edit add command -label "Preferences" -command doprefs - .bar.edit configure -font $uifont + .bar.edit configure -font uifont - menu .bar.view -font $uifont + menu .bar.view -font uifont .bar add cascade -label "View" -menu .bar.view .bar.view add command -label "New view..." -command {newview 0} .bar.view add command -label "Edit view..." -command editview \ @@ -555,7 +657,7 @@ proc makewindow {} { .bar add cascade -label "Help" -menu .bar.help .bar.help add command -label "About gitk" -command about .bar.help add command -label "Key bindings" -command keys - .bar.help configure -font $uifont + .bar.help configure -font uifont . configure -menu .bar # the gui has upper and lower half, parts of a paned window. @@ -612,10 +714,10 @@ proc makewindow {} { set entries $sha1entry set sha1but .tf.bar.sha1label button $sha1but -text "SHA1 ID: " -state disabled -relief flat \ - -command gotocommit -width 8 -font $uifont + -command gotocommit -width 8 -font uifont $sha1but conf -disabledforeground [$sha1but cget -foreground] pack .tf.bar.sha1label -side left - entry $sha1entry -width 40 -font $textfont -textvariable sha1string + entry $sha1entry -width 40 -font textfont -textvariable sha1string trace add variable sha1string write sha1change pack $sha1entry -side left -pady 2 @@ -642,62 +744,61 @@ proc makewindow {} { -state disabled -width 26 pack .tf.bar.rightbut -side left -fill y - button .tf.bar.findbut -text "Find" -command dofind -font $uifont - pack .tf.bar.findbut -side left + # Status label and progress bar + set statusw .tf.bar.status + label $statusw -width 15 -relief sunken -font uifont + pack $statusw -side left -padx 5 + set h [expr {[font metrics uifont -linespace] + 2}] + set progresscanv .tf.bar.progress + canvas $progresscanv -relief sunken -height $h -borderwidth 2 + set progressitem [$progresscanv create rect -1 0 0 $h -fill green] + set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] + set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] + pack $progresscanv -side right -expand 1 -fill x + set progresscoords {0 0} + set fprogcoord 0 + set rprogcoord 0 + bind $progresscanv <Configure> adjustprogress + set lastprogupdate [clock clicks -milliseconds] + set progupdatepending 0 + + # build up the bottom bar of upper window + label .tf.lbar.flabel -text "Find " -font uifont + button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont + button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont + label .tf.lbar.flab2 -text " commit " -font uifont + pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \ + -side left -fill y + set gdttype "containing:" + set gm [tk_optionMenu .tf.lbar.gdttype gdttype \ + "containing:" \ + "touching paths:" \ + "adding/removing string:"] + trace add variable gdttype write gdttype_change + $gm conf -font uifont + .tf.lbar.gdttype conf -font uifont + pack .tf.lbar.gdttype -side left -fill y + set findstring {} - set fstring .tf.bar.findstring + set fstring .tf.lbar.findstring lappend entries $fstring - entry $fstring -width 30 -font $textfont -textvariable findstring + entry $fstring -width 30 -font textfont -textvariable findstring trace add variable findstring write find_change - pack $fstring -side left -expand 1 -fill x -in .tf.bar set findtype Exact - set findtypemenu [tk_optionMenu .tf.bar.findtype \ + set findtypemenu [tk_optionMenu .tf.lbar.findtype \ findtype Exact IgnCase Regexp] - trace add variable findtype write find_change - .tf.bar.findtype configure -font $uifont - .tf.bar.findtype.menu configure -font $uifont + trace add variable findtype write findcom_change + .tf.lbar.findtype configure -font uifont + .tf.lbar.findtype.menu configure -font uifont set findloc "All fields" - tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \ + tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \ Comments Author Committer trace add variable findloc write find_change - .tf.bar.findloc configure -font $uifont - .tf.bar.findloc.menu configure -font $uifont - pack .tf.bar.findloc -side right - pack .tf.bar.findtype -side right - - # build up the bottom bar of upper window - label .tf.lbar.flabel -text "Highlight: Commits " \ - -font $uifont - pack .tf.lbar.flabel -side left -fill y - set gdttype "touching paths:" - set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \ - "adding/removing string:"] - trace add variable gdttype write hfiles_change - $gm conf -font $uifont - .tf.lbar.gdttype conf -font $uifont - pack .tf.lbar.gdttype -side left -fill y - entry .tf.lbar.fent -width 25 -font $textfont \ - -textvariable highlight_files - trace add variable highlight_files write hfiles_change - lappend entries .tf.lbar.fent - pack .tf.lbar.fent -side left -fill x -expand 1 - label .tf.lbar.vlabel -text " OR in view" -font $uifont - pack .tf.lbar.vlabel -side left -fill y - global viewhlmenu selectedhlview - set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None] - $viewhlmenu entryconf None -command delvhighlight - $viewhlmenu conf -font $uifont - .tf.lbar.vhl conf -font $uifont - pack .tf.lbar.vhl -side left -fill y - label .tf.lbar.rlabel -text " OR " -font $uifont - pack .tf.lbar.rlabel -side left -fill y - global highlight_related - set m [tk_optionMenu .tf.lbar.relm highlight_related None \ - "Descendent" "Not descendent" "Ancestor" "Not ancestor"] - $m conf -font $uifont - .tf.lbar.relm conf -font $uifont - trace add variable highlight_related write vrel_change - pack .tf.lbar.relm -side left -fill y + .tf.lbar.findloc configure -font uifont + .tf.lbar.findloc.menu configure -font uifont + pack .tf.lbar.findloc -side right + pack .tf.lbar.findtype -side right + pack $fstring -side left -expand 1 -fill x # Finish putting the upper half of the viewer together pack .tf.lbar -in .tf -side bottom -fill x @@ -722,23 +823,23 @@ proc makewindow {} { frame .bleft.mid button .bleft.top.search -text "Search" -command dosearch \ - -font $uifont + -font uifont pack .bleft.top.search -side left -padx 5 set sstring .bleft.top.sstring - entry $sstring -width 20 -font $textfont -textvariable searchstring + entry $sstring -width 20 -font textfont -textvariable searchstring lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x - radiobutton .bleft.mid.diff -text "Diff" \ + radiobutton .bleft.mid.diff -text "Diff" -font uifont \ -command changediffdisp -variable diffelide -value {0 0} - radiobutton .bleft.mid.old -text "Old version" \ + radiobutton .bleft.mid.old -text "Old version" -font uifont \ -command changediffdisp -variable diffelide -value {0 1} - radiobutton .bleft.mid.new -text "New version" \ + radiobutton .bleft.mid.new -text "New version" -font uifont \ -command changediffdisp -variable diffelide -value {1 0} label .bleft.mid.labeldiffcontext -text " Lines of context: " \ - -font $uifont + -font uifont pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left - spinbox .bleft.mid.diffcontext -width 5 -font $textfont \ + spinbox .bleft.mid.diffcontext -width 5 -font textfont \ -from 1 -increment 1 -to 10000000 \ -validate all -validatecommand "diffcontextvalidate %P" \ -textvariable diffcontextstring @@ -748,9 +849,11 @@ proc makewindow {} { pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left set ctext .bleft.ctext text $ctext -background $bgcolor -foreground $fgcolor \ - -tabs "[expr {$tabstop * $charspc}]" \ - -state disabled -font $textfont \ + -state disabled -font textfont \ -yscrollcommand scrolltext -wrap none + if {$have_tk85} { + $ctext conf -tabstyle wordprocessor + } scrollbar .bleft.sb -command "$ctext yview" pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x @@ -760,7 +863,7 @@ proc makewindow {} { lappend fglist $ctext $ctext tag conf comment -wrap $wrapcomment - $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" + $ctext tag conf filesep -font textfontbold -back "#aaaaaa" $ctext tag conf hunksep -fore [lindex $diffcolors 2] $ctext tag conf d0 -fore [lindex $diffcolors 0] $ctext tag conf d1 -fore [lindex $diffcolors 1] @@ -782,8 +885,8 @@ proc makewindow {} { $ctext tag conf m15 -fore "#ff70b0" $ctext tag conf mmax -fore darkgrey set mergemax 16 - $ctext tag conf mresult -font [concat $textfont bold] - $ctext tag conf msep -font [concat $textfont bold] + $ctext tag conf mresult -font textfontbold + $ctext tag conf msep -font textfontbold $ctext tag conf found -back yellow .pwbottom add .bleft @@ -794,18 +897,18 @@ proc makewindow {} { frame .bright.mode radiobutton .bright.mode.patch -text "Patch" \ -command reselectline -variable cmitmode -value "patch" - .bright.mode.patch configure -font $uifont + .bright.mode.patch configure -font uifont radiobutton .bright.mode.tree -text "Tree" \ -command reselectline -variable cmitmode -value "tree" - .bright.mode.tree configure -font $uifont + .bright.mode.tree configure -font uifont grid .bright.mode.patch .bright.mode.tree -sticky ew pack .bright.mode -side top -fill x set cflist .bright.cfiles - set indent [font measure $mainfont "nn"] + set indent [font measure mainfont "nn"] text $cflist \ -selectbackground $selectbgcolor \ -background $bgcolor -foreground $fgcolor \ - -font $mainfont \ + -font mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ -yscrollcommand ".bright.sb set" \ -cursor [. cget -cursor] \ @@ -817,7 +920,7 @@ proc makewindow {} { pack $cflist -side left -fill both -expand 1 $cflist tag configure highlight \ -background [$cflist cget -selectbackground] - $cflist tag configure bold -font [concat $mainfont bold] + $cflist tag configure bold -font mainfontbold .pwbottom add .bright .ctop add .pwbottom @@ -843,6 +946,12 @@ proc makewindow {} { } else { bindall <ButtonRelease-4> "allcanvs yview scroll -5 units" bindall <ButtonRelease-5> "allcanvs yview scroll 5 units" + if {[tk windowingsystem] eq "aqua"} { + bindall <MouseWheel> { + set delta [expr {- (%D)}] + allcanvs yview scroll $delta units + } + } } bindall <2> "canvscan mark %W %x %y" bindall <B2-Motion> "canvscan dragto %W %x %y" @@ -850,8 +959,8 @@ proc makewindow {} { bindkey <End> sellastline bind . <Key-Up> "selnextline -1" bind . <Key-Down> "selnextline 1" - bind . <Shift-Key-Up> "next_highlight -1" - bind . <Shift-Key-Down> "next_highlight 1" + bind . <Shift-Key-Up> "dofind -1 0" + bind . <Shift-Key-Down> "dofind 1 0" bindkey <Key-Right> "goforw" bindkey <Key-Left> "goback" bind . <Key-Prior> "selnextpage -1" @@ -876,14 +985,14 @@ proc makewindow {} { bindkey b "$ctext yview scroll -1 pages" bindkey d "$ctext yview scroll 18 units" bindkey u "$ctext yview scroll -18 units" - bindkey / {findnext 1} - bindkey <Key-Return> {findnext 0} - bindkey ? findprev + bindkey / {dofind 1 1} + bindkey <Key-Return> {dofind 1 1} + bindkey ? {dofind -1 1} bindkey f nextfile bindkey <F5> updatecommits bind . <$M1B-q> doquit - bind . <$M1B-f> dofind - bind . <$M1B-g> {findnext 0} + bind . <$M1B-f> {dofind 1 1} + bind . <$M1B-g> {dofind 1 0} bind . <$M1B-r> dosearchback bind . <$M1B-s> dosearch bind . <$M1B-equal> {incrfont 1} @@ -892,7 +1001,7 @@ proc makewindow {} { bind . <$M1B-KP_Subtract> {incrfont -1} wm protocol . WM_DELETE_WINDOW doquit bind . <Button-1> "click %W" - bind $fstring <Key-Return> dofind + bind $fstring <Key-Return> {dofind 1 1} bind $sha1entry <Key-Return> gotocommit bind $sha1entry <<PasteSelection>> clearsha1 bind $cflist <1> {sel_flist %W %x %y; break} @@ -1008,12 +1117,45 @@ proc click {w} { focus . } +# Adjust the progress bar for a change in requested extent or canvas size +proc adjustprogress {} { + global progresscanv progressitem progresscoords + global fprogitem fprogcoord lastprogupdate progupdatepending + global rprogitem rprogcoord + + set w [expr {[winfo width $progresscanv] - 4}] + set x0 [expr {$w * [lindex $progresscoords 0]}] + set x1 [expr {$w * [lindex $progresscoords 1]}] + set h [winfo height $progresscanv] + $progresscanv coords $progressitem $x0 0 $x1 $h + $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h + $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h + set now [clock clicks -milliseconds] + if {$now >= $lastprogupdate + 100} { + set progupdatepending 0 + update + } elseif {!$progupdatepending} { + set progupdatepending 1 + after [expr {$lastprogupdate + 100 - $now}] doprogupdate + } +} + +proc doprogupdate {} { + global lastprogupdate progupdatepending + + if {$progupdatepending} { + set progupdatepending 0 + set lastprogupdate [clock clicks -milliseconds] + update + } +} + proc savestuff {w} { - global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop + global canv canv2 canv3 mainfont textfont uifont tabstop global stuffsaved findmergefiles maxgraphpct global maxwidth showneartags showlocalchanges global viewname viewfiles viewargs viewperm nextviewnum - global cmitmode wrapcomment datetimeformat + global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor if {$stuffsaved} return @@ -1032,6 +1174,7 @@ proc savestuff {w} { puts $f [list set showneartags $showneartags] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] + puts $f [list set limitdiffs $limitdiffs] puts $f [list set bgcolor $bgcolor] puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] @@ -1143,10 +1286,10 @@ Copyright © 2005-2006 Paul Mackerras Use and redistribute under the terms of the GNU General Public License} \ -justify center -aspect 400 -border 2 -bg white -relief groove pack $w.m -side top -fill x -padx 2 -pady 2 - $w.m configure -font $uifont + $w.m configure -font uifont button $w.ok -text Close -command "destroy $w" -default active pack $w.ok -side bottom - $w.ok configure -font $uifont + $w.ok configure -font uifont bind $w <Visibility> "focus $w.ok" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> "destroy $w" @@ -1184,8 +1327,8 @@ Gitk key bindings: <$M1T-Down> Scroll commit list down one line <$M1T-PageUp> Scroll commit list up one page <$M1T-PageDown> Scroll commit list down one page -<Shift-Up> Move to previous highlighted line -<Shift-Down> Move to next highlighted line +<Shift-Up> Find backwards (upwards, later commits) +<Shift-Down> Find forwards (downwards, earlier commits) <Delete>, b Scroll diff view up one page <Backspace> Scroll diff view up one page <Space> Scroll diff view down one page @@ -1207,10 +1350,10 @@ f Scroll diff view to next file " \ -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 - $w.m configure -font $uifont + $w.m configure -font uifont button $w.ok -text Close -command "destroy $w" -default active pack $w.ok -side bottom - $w.ok configure -font $uifont + $w.ok configure -font uifont bind $w <Visibility> "focus $w.ok" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> "destroy $w" @@ -1583,6 +1726,7 @@ proc pop_flist_menu {w X Y x y} { global ctext cflist cmitmode flist_menu flist_menu_file global treediffs diffids + stopfinding set l [lindex [split [$w index "@$x,$y"] "."] 0] if {$l <= 1} return if {$cmitmode eq "tree"} { @@ -1596,14 +1740,15 @@ proc pop_flist_menu {w X Y x y} { } proc flist_hl {only} { - global flist_menu_file highlight_files + global flist_menu_file findstring gdttype set x [shellquote $flist_menu_file] - if {$only || $highlight_files eq {}} { - set highlight_files $x + if {$only || $findstring eq {} || $gdttype ne "touching paths:"} { + set findstring $x } else { - append highlight_files " " $x + append findstring " " $x } + set gdttype "touching paths:" } # Functions for adding and removing shell-type quoting @@ -1740,22 +1885,22 @@ proc vieweditor {top n title} { toplevel $top wm title $top $title - label $top.nl -text "Name" -font $uifont - entry $top.name -width 20 -textvariable newviewname($n) -font $uifont + label $top.nl -text "Name" -font uifont + entry $top.name -width 20 -textvariable newviewname($n) -font uifont grid $top.nl $top.name -sticky w -pady 5 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \ - -font $uifont + -font uifont grid $top.perm - -pady 5 -sticky w - message $top.al -aspect 1000 -font $uifont \ + message $top.al -aspect 1000 -font uifont \ -text "Commits to include (arguments to git rev-list):" grid $top.al - -sticky w -pady 5 entry $top.args -width 50 -textvariable newviewargs($n) \ - -background white -font $uifont + -background white -font uifont grid $top.args - -sticky ew -padx 5 - message $top.l -aspect 1000 -font $uifont \ + message $top.l -aspect 1000 -font uifont \ -text "Enter files and directories to include, one per line:" grid $top.l - -sticky w - text $top.t -width 40 -height 10 -background white -font $uifont + text $top.t -width 40 -height 10 -background white -font uifont if {[info exists viewfiles($n)]} { foreach f $viewfiles($n) { $top.t insert end $f @@ -1767,9 +1912,9 @@ proc vieweditor {top n title} { grid $top.t - -sticky ew -padx 5 frame $top.buts button $top.buts.ok -text "OK" -command [list newviewok $top $n] \ - -font $uifont + -font uifont button $top.buts.can -text "Cancel" -command [list destroy $top] \ - -font $uifont + -font uifont grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -1788,10 +1933,10 @@ proc doviewmenu {m first cmd op argv} { } proc allviewmenus {n op args} { - global viewhlmenu + # global viewhlmenu doviewmenu .bar.view 5 [list showview $n] $op $args - doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args + # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } proc newviewok {top n} { @@ -1834,8 +1979,8 @@ proc newviewok {top n} { set viewname($n) $newviewname($n) doviewmenu .bar.view 5 [list showview $n] \ entryconf [list -label $viewname($n)] - doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ - entryconf [list -label $viewname($n) -value $viewname($n)] + # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ + # entryconf [list -label $viewname($n) -value $viewname($n)] } if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} { set viewfiles($n) $files @@ -1867,8 +2012,8 @@ proc addviewmenu {n} { .bar.view add radiobutton -label $viewname($n) \ -command [list showview $n] -variable selectedview -value $n - $viewhlmenu add radiobutton -label $viewname($n) \ - -command [list addvhighlight $n] -variable selectedhlview + #$viewhlmenu add radiobutton -label $viewname($n) \ + # -command [list addvhighlight $n] -variable selectedhlview } proc flatten {var} { @@ -1892,17 +2037,17 @@ proc unflatten {var l} { proc showview {n} { global curview viewdata viewfiles - global displayorder parentlist rowidlist rowoffsets + global displayorder parentlist rowidlist rowisopt rowfinal global colormap rowtextx commitrow nextcolor canvxmax - global numcommits rowrangelist commitlisted idrowranges rowchk + global numcommits commitlisted global selectedline currentid canv canvy0 global treediffs global pending_select phase - global commitidx rowlaidout rowoptim + global commitidx global commfd global selectedview selectfirst global vparentlist vdisporder vcmitlisted - global hlview selectedhlview + global hlview selectedhlview commitinterest if {$n == $curview} return set selid {} @@ -1928,15 +2073,11 @@ proc showview {n} { set vparentlist($curview) $parentlist set vdisporder($curview) $displayorder set vcmitlisted($curview) $commitlisted - if {$phase ne {}} { - set viewdata($curview) \ - [list $phase $rowidlist $rowoffsets $rowrangelist \ - [flatten idrowranges] [flatten idinlist] \ - $rowlaidout $rowoptim $numcommits] - } elseif {![info exists viewdata($curview)] - || [lindex $viewdata($curview) 0] ne {}} { + if {$phase ne {} || + ![info exists viewdata($curview)] || + [lindex $viewdata($curview) 0] ne {}} { set viewdata($curview) \ - [list {} $rowidlist $rowoffsets $rowrangelist] + [list $phase $rowidlist $rowisopt $rowfinal] } } catch {unset treediffs} @@ -1945,12 +2086,14 @@ proc showview {n} { unset hlview set selectedhlview None } + catch {unset commitinterest} set curview $n set selectedview $n .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}] .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}] + run refill_reflist if {![info exists viewdata($n)]} { if {$selid ne {}} { set pending_select $selid @@ -1965,19 +2108,9 @@ proc showview {n} { set parentlist $vparentlist($n) set commitlisted $vcmitlisted($n) set rowidlist [lindex $v 1] - set rowoffsets [lindex $v 2] - set rowrangelist [lindex $v 3] - if {$phase eq {}} { - set numcommits [llength $displayorder] - catch {unset idrowranges} - } else { - unflatten idrowranges [lindex $v 4] - unflatten idinlist [lindex $v 5] - set rowlaidout [lindex $v 6] - set rowoptim [lindex $v 7] - set numcommits [lindex $v 8] - catch {unset rowchk} - } + set rowisopt [lindex $v 2] + set rowfinal [lindex $v 3] + set numcommits $commitidx($n) catch {unset colormap} catch {unset rowtextx} @@ -2021,7 +2154,6 @@ proc showview {n} { } elseif {$numcommits == 0} { show_status "No commits selected" } - run refill_reflist } # Stuff relating to the highlighting facility @@ -2073,12 +2205,12 @@ proc bolden_name {row font} { } proc unbolden {} { - global mainfont boldrows + global boldrows set stillbold {} foreach row $boldrows { if {![ishighlighted $row]} { - bolden $row $mainfont + bolden $row mainfont } else { lappend stillbold $row } @@ -2094,7 +2226,7 @@ proc addvhighlight {n} { } set hlview $n if {$n != $curview && ![info exists viewdata($n)]} { - set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}] + set viewdata($n) [list getcommits {{}} 0 0 0] set vparentlist($n) {} set vdisporder($n) {} set vcmitlisted($n) {} @@ -2117,9 +2249,8 @@ proc delvhighlight {} { proc vhighlightmore {} { global hlview vhl_done commitidx vhighlights - global displayorder vdisporder curview mainfont + global displayorder vdisporder curview - set font [concat $mainfont bold] set max $commitidx($hlview) if {$hlview == $curview} { set disp $displayorder @@ -2135,7 +2266,7 @@ proc vhighlightmore {} { set row $commitrow($curview,$id) if {$r0 <= $row && $row <= $r1} { if {![highlighted $row]} { - bolden $row $font + bolden $row mainfontbold } set vhighlights($row) 1 } @@ -2145,11 +2276,11 @@ proc vhighlightmore {} { } proc askvhighlight {row id} { - global hlview vhighlights commitrow iddrawn mainfont + global hlview vhighlights commitrow iddrawn if {[info exists commitrow($hlview,$id)]} { if {[info exists iddrawn($id)] && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } set vhighlights($row) 1 } else { @@ -2157,9 +2288,9 @@ proc askvhighlight {row id} { } } -proc hfiles_change {name ix op} { +proc hfiles_change {} { global highlight_files filehighlight fhighlights fh_serial - global mainfont highlight_paths + global highlight_paths gdttype if {[info exists filehighlight]} { # delete previous highlights @@ -2177,6 +2308,69 @@ proc hfiles_change {name ix op} { } } +proc gdttype_change {name ix op} { + global gdttype highlight_files findstring findpattern + + stopfinding + if {$findstring ne {}} { + if {$gdttype eq "containing:"} { + if {$highlight_files ne {}} { + set highlight_files {} + hfiles_change + } + findcom_change + } else { + if {$findpattern ne {}} { + set findpattern {} + findcom_change + } + set highlight_files $findstring + hfiles_change + } + drawvisible + } + # enable/disable findtype/findloc menus too +} + +proc find_change {name ix op} { + global gdttype findstring highlight_files + + stopfinding + if {$gdttype eq "containing:"} { + findcom_change + } else { + if {$highlight_files ne $findstring} { + set highlight_files $findstring + hfiles_change + } + } + drawvisible +} + +proc findcom_change args { + global nhighlights boldnamerows + global findpattern findtype findstring gdttype + + stopfinding + # delete previous highlights, if any + foreach row $boldnamerows { + bolden_name $row mainfont + } + set boldnamerows {} + catch {unset nhighlights} + unbolden + unmarkmatches + if {$gdttype ne "containing:" || $findstring eq {}} { + set findpattern {} + } elseif {$findtype eq "Regexp"} { + set findpattern $findstring + } else { + set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ + $findstring] + set findpattern "*$e*" + } +} + proc makepatterns {l} { set ret {} foreach e $l { @@ -2199,8 +2393,11 @@ proc do_file_hl {serial} { set highlight_paths [makepatterns $paths] highlight_filelist set gdtargs [concat -- $paths] - } else { + } elseif {$gdttype eq "adding/removing string:"} { set gdtargs [list "-S$highlight_files"] + } else { + # must be "containing:", i.e. we're searching commit info + return } set cmd [concat | git diff-tree -r -s --stdin $gdtargs] set filehighlight [open $cmd r+] @@ -2230,8 +2427,8 @@ proc askfilehighlight {row id} { } proc readfhighlight {} { - global filehighlight fhighlights commitrow curview mainfont iddrawn - global fhl_list + global filehighlight fhighlights commitrow curview iddrawn + global fhl_list find_dirn if {![info exists filehighlight]} { return 0 @@ -2252,7 +2449,7 @@ proc readfhighlight {} { if {![info exists commitrow($curview,$line)]} continue set row $commitrow($curview,$line) if {[info exists iddrawn($line)] && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } set fhighlights($row) 1 } @@ -2263,35 +2460,17 @@ proc readfhighlight {} { unset filehighlight return 0 } - next_hlcont - return 1 -} - -proc find_change {name ix op} { - global nhighlights mainfont boldnamerows - global findstring findpattern findtype - - # delete previous highlights, if any - foreach row $boldnamerows { - bolden_name $row $mainfont - } - set boldnamerows {} - catch {unset nhighlights} - unbolden - unmarkmatches - if {$findtype ne "Regexp"} { - set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ - $findstring] - set findpattern "*$e*" + if {[info exists find_dirn]} { + run findmore } - drawvisible + return 1 } proc doesmatch {f} { - global findtype findstring findpattern + global findtype findpattern if {$findtype eq "Regexp"} { - return [regexp $findstring $f] + return [regexp $findpattern $f] } elseif {$findtype eq "IgnCase"} { return [string match -nocase $findpattern $f] } else { @@ -2300,7 +2479,7 @@ proc doesmatch {f} { } proc askfindhighlight {row id} { - global nhighlights commitinfo iddrawn mainfont + global nhighlights commitinfo iddrawn global findloc global markingmatches @@ -2321,11 +2500,10 @@ proc askfindhighlight {row id} { } } if {$isbold && [info exists iddrawn($id)]} { - set f [concat $mainfont bold] if {![ishighlighted $row]} { - bolden $row $f + bolden $row mainfontbold if {$isbold > 1} { - bolden_name $row $f + bolden_name $row mainfontbold } } if {$markingmatches} { @@ -2454,7 +2632,7 @@ proc is_ancestor {a} { } proc askrelhighlight {row id} { - global descendent highlight_related iddrawn mainfont rhighlights + global descendent highlight_related iddrawn rhighlights global selectedline ancestor if {![info exists selectedline]} return @@ -2478,87 +2656,12 @@ proc askrelhighlight {row id} { } if {[info exists iddrawn($id)]} { if {$isbold && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } } set rhighlights($row) $isbold } -proc next_hlcont {} { - global fhl_row fhl_dirn displayorder numcommits - global vhighlights fhighlights nhighlights rhighlights - global hlview filehighlight findstring highlight_related - - if {![info exists fhl_dirn] || $fhl_dirn == 0} return - set row $fhl_row - while {1} { - if {$row < 0 || $row >= $numcommits} { - bell - set fhl_dirn 0 - return - } - set id [lindex $displayorder $row] - if {[info exists hlview]} { - if {![info exists vhighlights($row)]} { - askvhighlight $row $id - } - if {$vhighlights($row) > 0} break - } - if {$findstring ne {}} { - if {![info exists nhighlights($row)]} { - askfindhighlight $row $id - } - if {$nhighlights($row) > 0} break - } - if {$highlight_related ne "None"} { - if {![info exists rhighlights($row)]} { - askrelhighlight $row $id - } - if {$rhighlights($row) > 0} break - } - if {[info exists filehighlight]} { - if {![info exists fhighlights($row)]} { - # ask for a few more while we're at it... - set r $row - for {set n 0} {$n < 100} {incr n} { - if {![info exists fhighlights($r)]} { - askfilehighlight $r [lindex $displayorder $r] - } - incr r $fhl_dirn - if {$r < 0 || $r >= $numcommits} break - } - flushhighlights - } - if {$fhighlights($row) < 0} { - set fhl_row $row - return - } - if {$fhighlights($row) > 0} break - } - incr row $fhl_dirn - } - set fhl_dirn 0 - selectline $row 1 -} - -proc next_highlight {dirn} { - global selectedline fhl_row fhl_dirn - global hlview filehighlight findstring highlight_related - - if {![info exists selectedline]} return - if {!([info exists hlview] || $findstring ne {} || - $highlight_related ne "None" || [info exists filehighlight])} return - set fhl_row [expr {$selectedline + $dirn}] - set fhl_dirn $dirn - next_hlcont -} - -proc cancel_next_highlight {} { - global fhl_dirn - - set fhl_dirn 0 -} - # Graph layout functions proc shortids {ids} { @@ -2575,108 +2678,43 @@ proc shortids {ids} { return $res } -proc incrange {l x o} { - set n [llength $l] - while {$x < $n} { - set e [lindex $l $x] - if {$e ne {}} { - lset l $x [expr {$e + $o}] - } - incr x - } - return $l -} - proc ntimes {n o} { set ret {} - for {} {$n > 0} {incr n -1} { - lappend ret $o - } - return $ret -} - -proc usedinrange {id l1 l2} { - global children commitrow curview - - if {[info exists commitrow($curview,$id)]} { - set r $commitrow($curview,$id) - if {$l1 <= $r && $r <= $l2} { - return [expr {$r - $l1 + 1}] - } - } - set kids $children($curview,$id) - foreach c $kids { - set r $commitrow($curview,$c) - if {$l1 <= $r && $r <= $l2} { - return [expr {$r - $l1 + 1}] + set o [list $o] + for {set mask 1} {$mask <= $n} {incr mask $mask} { + if {($n & $mask) != 0} { + set ret [concat $ret $o] } + set o [concat $o $o] } - return 0 + return $ret } -proc sanity {row {full 0}} { - global rowidlist rowoffsets +# Work out where id should go in idlist so that order-token +# values increase from left to right +proc idcol {idlist id {i 0}} { + global ordertok curview - set col -1 - set ids [lindex $rowidlist $row] - foreach id $ids { - incr col - if {$id eq {}} continue - if {$col < [llength $ids] - 1 && - [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} { - puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}" - } - set o [lindex $rowoffsets $row $col] - set y $row - set x $col - while {$o ne {}} { - incr y -1 - incr x $o - if {[lindex $rowidlist $y $x] != $id} { - puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]" - puts " id=[shortids $id] check started at row $row" - for {set i $row} {$i >= $y} {incr i -1} { - puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}" - } - break - } - if {!$full} break - set o [lindex $rowoffsets $y $x] + set t $ordertok($curview,$id) + if {$i >= [llength $idlist] || + $t < $ordertok($curview,[lindex $idlist $i])} { + if {$i > [llength $idlist]} { + set i [llength $idlist] } - } -} - -proc makeuparrow {oid x y z} { - global rowidlist rowoffsets uparrowlen idrowranges displayorder - - for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} { - incr y -1 - incr x $z - set off0 [lindex $rowoffsets $y] - for {set x0 $x} {1} {incr x0} { - if {$x0 >= [llength $off0]} { - set x0 [llength [lindex $rowoffsets [expr {$y-1}]]] - break - } - set z [lindex $off0 $x0] - if {$z ne {}} { - incr x0 $z - break - } + while {[incr i -1] >= 0 && + $t < $ordertok($curview,[lindex $idlist $i])} {} + incr i + } else { + if {$t > $ordertok($curview,[lindex $idlist $i])} { + while {[incr i] < [llength $idlist] && + $t >= $ordertok($curview,[lindex $idlist $i])} {} } - set z [expr {$x0 - $x}] - lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid] - lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z] } - set tmp [lreplace [lindex $rowoffsets $y] $x $x {}] - lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1] - lappend idrowranges($oid) [lindex $displayorder $y] + return $i } proc initlayout {} { - global rowidlist rowoffsets displayorder commitlisted - global rowlaidout rowoptim - global idinlist rowchk rowrangelist idrowranges + global rowidlist rowisopt rowfinal displayorder commitlisted global numcommits canvxmax canv global nextcolor global parentlist @@ -2687,18 +2725,13 @@ proc initlayout {} { set displayorder {} set commitlisted {} set parentlist {} - set rowrangelist {} set nextcolor 0 - set rowidlist {{}} - set rowoffsets {{}} - catch {unset idinlist} - catch {unset rowchk} - set rowlaidout 0 - set rowoptim 0 + set rowidlist {} + set rowisopt {} + set rowfinal {} set canvxmax [$canv cget -width] catch {unset colormap} catch {unset rowtextx} - catch {unset idrowranges} set selectfirst 1 } @@ -2730,61 +2763,19 @@ proc visiblerows {} { return [list $r0 $r1] } -proc layoutmore {tmax allread} { - global rowlaidout rowoptim commitidx numcommits optim_delay - global uparrowlen curview rowidlist idinlist +proc layoutmore {} { + global commitidx viewcomplete numcommits + global uparrowlen downarrowlen mingaplen curview - set showlast 0 - set showdelay $optim_delay - set optdelay [expr {$uparrowlen + 1}] - while {1} { - if {$rowoptim - $showdelay > $numcommits} { - showstuff [expr {$rowoptim - $showdelay}] $showlast - } elseif {$rowlaidout - $optdelay > $rowoptim} { - set nr [expr {$rowlaidout - $optdelay - $rowoptim}] - if {$nr > 100} { - set nr 100 - } - optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}] - incr rowoptim $nr - } elseif {$commitidx($curview) > $rowlaidout} { - set nr [expr {$commitidx($curview) - $rowlaidout}] - # may need to increase this threshold if uparrowlen or - # mingaplen are increased... - if {$nr > 150} { - set nr 150 - } - set row $rowlaidout - set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread] - if {$rowlaidout == $row} { - return 0 - } - } elseif {$allread} { - set optdelay 0 - set nrows $commitidx($curview) - if {[lindex $rowidlist $nrows] ne {} || - [array names idinlist] ne {}} { - layouttail - set rowlaidout $commitidx($curview) - } elseif {$rowoptim == $nrows} { - set showdelay 0 - set showlast 1 - if {$numcommits == $nrows} { - return 0 - } - } - } else { - return 0 - } - if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} { - return 1 - } + set show $commitidx($curview) + if {$show > $numcommits || $viewcomplete($curview)} { + showstuff $show $viewcomplete($curview) } } proc showstuff {canshow last} { global numcommits commitrow pending_select selectedline curview - global lookingforhead mainheadid displayorder selectfirst + global mainheadid displayorder selectfirst global lastscrollset commitinterest if {$numcommits == 0} { @@ -2792,15 +2783,6 @@ proc showstuff {canshow last} { set phase "incrdraw" allcanvs delete all } - for {set l $numcommits} {$l < $canshow} {incr l} { - set id [lindex $displayorder $l] - if {[info exists commitinterest($id)]} { - foreach script $commitinterest($id) { - eval [string map [list "%I" $id] $script] - } - unset commitinterest($id) - } - } set r0 $numcommits set prev $numcommits set numcommits $canshow @@ -2831,28 +2813,22 @@ proc showstuff {canshow last} { set selectfirst 0 } } - if {$lookingforhead && [info exists commitrow($curview,$mainheadid)] - && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} { - set lookingforhead 0 - dodiffindex - } } proc doshowlocalchanges {} { - global lookingforhead curview mainheadid phase commitrow + global curview mainheadid phase commitrow if {[info exists commitrow($curview,$mainheadid)] && ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} { dodiffindex } elseif {$phase ne {}} { - set lookingforhead 1 + lappend commitinterest($mainheadid) {} } } proc dohidelocalchanges {} { - global lookingforhead localfrow localirow lserial + global localfrow localirow lserial - set lookingforhead 0 if {$localfrow >= 0} { removerow $localfrow set localfrow -1 @@ -2869,8 +2845,9 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { - global localirow localfrow lserial + global localirow localfrow lserial showlocalchanges + if {!$showlocalchanges} return incr lserial set localfrow -1 set localirow -1 @@ -2941,207 +2918,325 @@ proc readdifffiles {fd serial} { return 0 } -proc layoutrows {row endrow last} { - global rowidlist rowoffsets displayorder - global uparrowlen downarrowlen maxwidth mingaplen - global children parentlist - global idrowranges - global commitidx curview - global idinlist rowchk rowrangelist +proc nextuse {id row} { + global commitrow curview children - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] - while {$row < $endrow} { - set id [lindex $displayorder $row] - set nev [expr {[llength $idlist] - $maxwidth + 1}] - foreach p [lindex $parentlist $row] { - if {![info exists idinlist($p)] || !$idinlist($p)} { - incr nev - } - } - if {$nev > 0} { - if {!$last && - $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break - for {set x [llength $idlist]} {[incr x -1] >= 0} {} { - set i [lindex $idlist $x] - if {![info exists rowchk($i)] || $row >= $rowchk($i)} { - set r [usedinrange $i [expr {$row - $downarrowlen}] \ - [expr {$row + $uparrowlen + $mingaplen}]] - if {$r == 0} { - set idlist [lreplace $idlist $x $x] - set offs [lreplace $offs $x $x] - set offs [incrange $offs $x 1] - set idinlist($i) 0 - set rm1 [expr {$row - 1}] - lappend idrowranges($i) [lindex $displayorder $rm1] - if {[incr nev -1] <= 0} break - continue - } - set rowchk($i) [expr {$row + $r}] - } + if {[info exists children($curview,$id)]} { + foreach kid $children($curview,$id) { + if {![info exists commitrow($curview,$kid)]} { + return -1 + } + if {$commitrow($curview,$kid) > $row} { + return $commitrow($curview,$kid) } - lset rowidlist $row $idlist - lset rowoffsets $row $offs } - set oldolds {} - set newolds {} - foreach p [lindex $parentlist $row] { - if {![info exists idinlist($p)]} { - lappend newolds $p - } elseif {!$idinlist($p)} { - lappend oldolds $p + } + if {[info exists commitrow($curview,$id)]} { + return $commitrow($curview,$id) + } + return -1 +} + +proc prevuse {id row} { + global commitrow curview children + + set ret -1 + if {[info exists children($curview,$id)]} { + foreach kid $children($curview,$id) { + if {![info exists commitrow($curview,$kid)]} break + if {$commitrow($curview,$kid) < $row} { + set ret $commitrow($curview,$kid) } - set idinlist($p) 1 } - set col [lsearch -exact $idlist $id] - if {$col < 0} { - set col [llength $idlist] - lappend idlist $id - lset rowidlist $row $idlist - set z {} - if {$children($curview,$id) ne {}} { - set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}] - unset idinlist($id) - } - lappend offs $z - lset rowoffsets $row $offs - if {$z ne {}} { - makeuparrow $id $col $row $z + } + return $ret +} + +proc make_idlist {row} { + global displayorder parentlist uparrowlen downarrowlen mingaplen + global commitidx curview ordertok children commitrow + + set r [expr {$row - $mingaplen - $downarrowlen - 1}] + if {$r < 0} { + set r 0 + } + set ra [expr {$row - $downarrowlen}] + if {$ra < 0} { + set ra 0 + } + set rb [expr {$row + $uparrowlen}] + if {$rb > $commitidx($curview)} { + set rb $commitidx($curview) + } + set ids {} + for {} {$r < $ra} {incr r} { + set nextid [lindex $displayorder [expr {$r + 1}]] + foreach p [lindex $parentlist $r] { + if {$p eq $nextid} continue + set rn [nextuse $p $r] + if {$rn >= $row && + $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} { + lappend ids [list $ordertok($curview,$p) $p] } - } else { - unset idinlist($id) - } - set ranges {} - if {[info exists idrowranges($id)]} { - set ranges $idrowranges($id) - lappend ranges $id - unset idrowranges($id) - } - lappend rowrangelist $ranges - incr row - set offs [ntimes [llength $idlist] 0] - set l [llength $newolds] - set idlist [eval lreplace \$idlist $col $col $newolds] - set o 0 - if {$l != 1} { - set offs [lrange $offs 0 [expr {$col - 1}]] - foreach x $newolds { - lappend offs {} - incr o -1 - } - incr o - set tmp [expr {[llength $idlist] - [llength $offs]}] - if {$tmp > 0} { - set offs [concat $offs [ntimes $tmp $o]] + } + } + for {} {$r < $row} {incr r} { + set nextid [lindex $displayorder [expr {$r + 1}]] + foreach p [lindex $parentlist $r] { + if {$p eq $nextid} continue + set rn [nextuse $p $r] + if {$rn < 0 || $rn >= $row} { + lappend ids [list $ordertok($curview,$p) $p] } - } else { - lset offs $col {} } - foreach i $newolds { - set idrowranges($i) $id + } + set id [lindex $displayorder $row] + lappend ids [list $ordertok($curview,$id) $id] + while {$r < $rb} { + foreach p [lindex $parentlist $r] { + set firstkid [lindex $children($curview,$p) 0] + if {$commitrow($curview,$firstkid) < $row} { + lappend ids [list $ordertok($curview,$p) $p] + } } - incr col $l - foreach oid $oldolds { - set idlist [linsert $idlist $col $oid] - set offs [linsert $offs $col $o] - makeuparrow $oid $col $row $o - incr col + incr r + set id [lindex $displayorder $r] + if {$id ne {}} { + set firstkid [lindex $children($curview,$id) 0] + if {$firstkid ne {} && $commitrow($curview,$firstkid) < $row} { + lappend ids [list $ordertok($curview,$id) $id] + } } - lappend rowidlist $idlist - lappend rowoffsets $offs } - return $row + set idlist {} + foreach idx [lsort -unique $ids] { + lappend idlist [lindex $idx 1] + } + return $idlist } -proc addextraid {id row} { - global displayorder commitrow commitinfo - global commitidx commitlisted - global parentlist children curview +proc rowsequal {a b} { + while {[set i [lsearch -exact $a {}]] >= 0} { + set a [lreplace $a $i $i] + } + while {[set i [lsearch -exact $b {}]] >= 0} { + set b [lreplace $b $i $i] + } + return [expr {$a eq $b}] +} - incr commitidx($curview) - lappend displayorder $id - lappend commitlisted 0 - lappend parentlist {} - set commitrow($curview,$id) $row - readcommit $id - if {![info exists commitinfo($id)]} { - set commitinfo($id) {"No commit information available"} +proc makeupline {id row rend col} { + global rowidlist uparrowlen downarrowlen mingaplen + + for {set r $rend} {1} {set r $rstart} { + set rstart [prevuse $id $r] + if {$rstart < 0} return + if {$rstart < $row} break } - if {![info exists children($curview,$id)]} { - set children($curview,$id) {} + if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} { + set rstart [expr {$rend - $uparrowlen - 1}] + } + for {set r $rstart} {[incr r] <= $row} {} { + set idlist [lindex $rowidlist $r] + if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} { + set col [idcol $idlist $id $col] + lset rowidlist $r [linsert $idlist $col $id] + changedrow $r + } } } -proc layouttail {} { - global rowidlist rowoffsets idinlist commitidx curview - global idrowranges rowrangelist +proc layoutrows {row endrow} { + global rowidlist rowisopt rowfinal displayorder + global uparrowlen downarrowlen maxwidth mingaplen + global children parentlist + global commitidx viewcomplete curview commitrow - set row $commitidx($curview) - set idlist [lindex $rowidlist $row] - while {$idlist ne {}} { - set col [expr {[llength $idlist] - 1}] - set id [lindex $idlist $col] - addextraid $id $row - catch {unset idinlist($id)} - lappend idrowranges($id) $id - lappend rowrangelist $idrowranges($id) - unset idrowranges($id) - incr row - set offs [ntimes $col 0] - set idlist [lreplace $idlist $col $col] - lappend rowidlist $idlist - lappend rowoffsets $offs - } - - foreach id [array names idinlist] { - unset idinlist($id) - addextraid $id $row - lset rowidlist $row [list $id] - lset rowoffsets $row 0 - makeuparrow $id 0 $row 0 - lappend idrowranges($id) $id - lappend rowrangelist $idrowranges($id) - unset idrowranges($id) - incr row - lappend rowidlist {} - lappend rowoffsets {} + set idlist {} + if {$row > 0} { + set rm1 [expr {$row - 1}] + foreach id [lindex $rowidlist $rm1] { + if {$id ne {}} { + lappend idlist $id + } + } + set final [lindex $rowfinal $rm1] + } + for {} {$row < $endrow} {incr row} { + set rm1 [expr {$row - 1}] + if {$rm1 < 0 || $idlist eq {}} { + set idlist [make_idlist $row] + set final 1 + } else { + set id [lindex $displayorder $rm1] + set col [lsearch -exact $idlist $id] + set idlist [lreplace $idlist $col $col] + foreach p [lindex $parentlist $rm1] { + if {[lsearch -exact $idlist $p] < 0} { + set col [idcol $idlist $p $col] + set idlist [linsert $idlist $col $p] + # if not the first child, we have to insert a line going up + if {$id ne [lindex $children($curview,$p) 0]} { + makeupline $p $rm1 $row $col + } + } + } + set id [lindex $displayorder $row] + if {$row > $downarrowlen} { + set termrow [expr {$row - $downarrowlen - 1}] + foreach p [lindex $parentlist $termrow] { + set i [lsearch -exact $idlist $p] + if {$i < 0} continue + set nr [nextuse $p $termrow] + if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} { + set idlist [lreplace $idlist $i $i] + } + } + } + set col [lsearch -exact $idlist $id] + if {$col < 0} { + set col [idcol $idlist $id] + set idlist [linsert $idlist $col $id] + if {$children($curview,$id) ne {}} { + makeupline $id $rm1 $row $col + } + } + set r [expr {$row + $uparrowlen - 1}] + if {$r < $commitidx($curview)} { + set x $col + foreach p [lindex $parentlist $r] { + if {[lsearch -exact $idlist $p] >= 0} continue + set fk [lindex $children($curview,$p) 0] + if {$commitrow($curview,$fk) < $row} { + set x [idcol $idlist $p $x] + set idlist [linsert $idlist $x $p] + } + } + if {[incr r] < $commitidx($curview)} { + set p [lindex $displayorder $r] + if {[lsearch -exact $idlist $p] < 0} { + set fk [lindex $children($curview,$p) 0] + if {$fk ne {} && $commitrow($curview,$fk) < $row} { + set x [idcol $idlist $p $x] + set idlist [linsert $idlist $x $p] + } + } + } + } + } + if {$final && !$viewcomplete($curview) && + $row + $uparrowlen + $mingaplen + $downarrowlen + >= $commitidx($curview)} { + set final 0 + } + set l [llength $rowidlist] + if {$row == $l} { + lappend rowidlist $idlist + lappend rowisopt 0 + lappend rowfinal $final + } elseif {$row < $l} { + if {![rowsequal $idlist [lindex $rowidlist $row]]} { + lset rowidlist $row $idlist + changedrow $row + } + lset rowfinal $row $final + } else { + set pad [ntimes [expr {$row - $l}] {}] + set rowidlist [concat $rowidlist $pad] + lappend rowidlist $idlist + set rowfinal [concat $rowfinal $pad] + lappend rowfinal $final + set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]] + } + } + return $row +} + +proc changedrow {row} { + global displayorder iddrawn rowisopt need_redisplay + + set l [llength $rowisopt] + if {$row < $l} { + lset rowisopt $row 0 + if {$row + 1 < $l} { + lset rowisopt [expr {$row + 1}] 0 + if {$row + 2 < $l} { + lset rowisopt [expr {$row + 2}] 0 + } + } + } + set id [lindex $displayorder $row] + if {[info exists iddrawn($id)]} { + set need_redisplay 1 } } proc insert_pad {row col npad} { - global rowidlist rowoffsets + global rowidlist set pad [ntimes $npad {}] - lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad] - set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad] - lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]] + set idlist [lindex $rowidlist $row] + set bef [lrange $idlist 0 [expr {$col - 1}]] + set aft [lrange $idlist $col end] + set i [lsearch -exact $aft {}] + if {$i > 0} { + set aft [lreplace $aft $i $i] + } + lset rowidlist $row [concat $bef $pad $aft] + changedrow $row } proc optimize_rows {row col endrow} { - global rowidlist rowoffsets displayorder + global rowidlist rowisopt displayorder curview children - for {} {$row < $endrow} {incr row} { - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] + if {$row < 1} { + set row 1 + } + for {} {$row < $endrow} {incr row; set col 0} { + if {[lindex $rowisopt $row]} continue set haspad 0 - for {} {$col < [llength $offs]} {incr col} { - if {[lindex $idlist $col] eq {}} { + set y0 [expr {$row - 1}] + set ym [expr {$row - 2}] + set idlist [lindex $rowidlist $row] + set previdlist [lindex $rowidlist $y0] + if {$idlist eq {} || $previdlist eq {}} continue + if {$ym >= 0} { + set pprevidlist [lindex $rowidlist $ym] + if {$pprevidlist eq {}} continue + } else { + set pprevidlist {} + } + set x0 -1 + set xm -1 + for {} {$col < [llength $idlist]} {incr col} { + set id [lindex $idlist $col] + if {[lindex $previdlist $col] eq $id} continue + if {$id eq {}} { set haspad 1 continue } - set z [lindex $offs $col] - if {$z eq {}} continue + set x0 [lsearch -exact $previdlist $id] + if {$x0 < 0} continue + set z [expr {$x0 - $col}] set isarrow 0 - set x0 [expr {$col + $z}] - set y0 [expr {$row - 1}] - set z0 [lindex $rowoffsets $y0 $x0] + set z0 {} + if {$ym >= 0} { + set xm [lsearch -exact $pprevidlist $id] + if {$xm >= 0} { + set z0 [expr {$xm - $x0}] + } + } if {$z0 eq {}} { - set id [lindex $idlist $col] - set ranges [rowranges $id] - if {$ranges ne {} && $y0 > [lindex $ranges 0]} { + # if row y0 is the first child of $id then it's not an arrow + if {[lindex $children($curview,$id) 0] ne + [lindex $displayorder $y0]} { set isarrow 1 } } + if {!$isarrow && $id ne [lindex $displayorder $row] && + [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} { + set isarrow 1 + } # Looking at lines from this row to the previous row, # make them go straight up if they end in an arrow on # the previous row; otherwise make them go straight up @@ -3150,43 +3245,32 @@ proc optimize_rows {row col endrow} { # Line currently goes left too much; # insert pads in the previous row, then optimize it set npad [expr {-1 - $z + $isarrow}] - set offs [incrange $offs $col $npad] insert_pad $y0 $x0 $npad if {$y0 > 0} { optimize_rows $y0 $x0 $row } - set z [lindex $offs $col] - set x0 [expr {$col + $z}] - set z0 [lindex $rowoffsets $y0 $x0] + set previdlist [lindex $rowidlist $y0] + set x0 [lsearch -exact $previdlist $id] + set z [expr {$x0 - $col}] + if {$z0 ne {}} { + set pprevidlist [lindex $rowidlist $ym] + set xm [lsearch -exact $pprevidlist $id] + set z0 [expr {$xm - $x0}] + } } elseif {$z > 1 || ($z > 0 && $isarrow)} { # Line currently goes right too much; - # insert pads in this line and adjust the next's rowoffsets + # insert pads in this line set npad [expr {$z - 1 + $isarrow}] - set y1 [expr {$row + 1}] - set offs2 [lindex $rowoffsets $y1] - set x1 -1 - foreach z $offs2 { - incr x1 - if {$z eq {} || $x1 + $z < $col} continue - if {$x1 + $z > $col} { - incr npad - } - lset rowoffsets $y1 [incrange $offs2 $x1 $npad] - break - } - set pad [ntimes $npad {}] - set idlist [eval linsert \$idlist $col $pad] - set tmp [eval linsert \$offs $col $pad] + insert_pad $row $col $npad + set idlist [lindex $rowidlist $row] incr col $npad - set offs [incrange $tmp $col [expr {-$npad}]] - set z [lindex $offs $col] + set z [expr {$x0 - $col}] set haspad 1 } - if {$z0 eq {} && !$isarrow} { + if {$z0 eq {} && !$isarrow && $ym >= 0} { # this line links to its first child on row $row-2 - set rm2 [expr {$row - 2}] - set id [lindex $displayorder $rm2] - set xc [lsearch -exact [lindex $rowidlist $rm2] $id] + set id [lindex $displayorder $ym] + set xc [lsearch -exact $pprevidlist $id] if {$xc >= 0} { set z0 [expr {$xc - $x0}] } @@ -3194,52 +3278,35 @@ proc optimize_rows {row col endrow} { # avoid lines jigging left then immediately right if {$z0 ne {} && $z < 0 && $z0 > 0} { insert_pad $y0 $x0 1 - set offs [incrange $offs $col 1] - optimize_rows $y0 [expr {$x0 + 1}] $row + incr x0 + optimize_rows $y0 $x0 $row + set previdlist [lindex $rowidlist $y0] } } if {!$haspad} { - set o {} # Find the first column that doesn't have a line going right for {set col [llength $idlist]} {[incr col -1] >= 0} {} { - set o [lindex $offs $col] - if {$o eq {}} { + set id [lindex $idlist $col] + if {$id eq {}} break + set x0 [lsearch -exact $previdlist $id] + if {$x0 < 0} { # check if this is the link to the first child - set id [lindex $idlist $col] - set ranges [rowranges $id] - if {$ranges ne {} && $row == [lindex $ranges 0]} { + set kid [lindex $displayorder $y0] + if {[lindex $children($curview,$id) 0] eq $kid} { # it is, work out offset to child - set y0 [expr {$row - 1}] - set id [lindex $displayorder $y0] - set x0 [lsearch -exact [lindex $rowidlist $y0] $id] - if {$x0 >= 0} { - set o [expr {$x0 - $col}] - } + set x0 [lsearch -exact $previdlist $kid] } } - if {$o eq {} || $o <= 0} break + if {$x0 <= $col} break } # Insert a pad at that column as long as it has a line and - # isn't the last column, and adjust the next row' offsets - if {$o ne {} && [incr col] < [llength $idlist]} { - set y1 [expr {$row + 1}] - set offs2 [lindex $rowoffsets $y1] - set x1 -1 - foreach z $offs2 { - incr x1 - if {$z eq {} || $x1 + $z < $col} continue - lset rowoffsets $y1 [incrange $offs2 $x1 1] - break - } + # isn't the last column + if {$x0 >= 0 && [incr col] < [llength $idlist]} { set idlist [linsert $idlist $col {}] - set tmp [linsert $offs $col {}] - incr col - set offs [incrange $tmp $col -1] + lset rowidlist $row $idlist + changedrow $row } } - lset rowidlist $row $idlist - lset rowoffsets $row $offs - set col 0 } } @@ -3264,51 +3331,64 @@ proc linewidth {id} { } proc rowranges {id} { - global phase idrowranges commitrow rowlaidout rowrangelist curview - - set ranges {} - if {$phase eq {} || - ([info exists commitrow($curview,$id)] - && $commitrow($curview,$id) < $rowlaidout)} { - set ranges [lindex $rowrangelist $commitrow($curview,$id)] - } elseif {[info exists idrowranges($id)]} { - set ranges $idrowranges($id) - } - set linenos {} - foreach rid $ranges { - lappend linenos $commitrow($curview,$rid) - } - if {$linenos ne {}} { - lset linenos 0 [expr {[lindex $linenos 0] + 1}] - } - return $linenos -} - -# work around tk8.4 refusal to draw arrows on diagonal segments -proc adjarrowhigh {coords} { - global linespc - - set x0 [lindex $coords 0] - set x1 [lindex $coords 2] - if {$x0 != $x1} { - set y0 [lindex $coords 1] - set y1 [lindex $coords 3] - if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} { - # we have a nearby vertical segment, just trim off the diag bit - set coords [lrange $coords 2 end] + global commitrow curview children uparrowlen downarrowlen + global rowidlist + + set kids $children($curview,$id) + if {$kids eq {}} { + return {} + } + set ret {} + lappend kids $id + foreach child $kids { + if {![info exists commitrow($curview,$child)]} break + set row $commitrow($curview,$child) + if {![info exists prev]} { + lappend ret [expr {$row + 1}] } else { - set slope [expr {($x0 - $x1) / ($y0 - $y1)}] - set xi [expr {$x0 - $slope * $linespc / 2}] - set yi [expr {$y0 - $linespc / 2}] - set coords [lreplace $coords 0 1 $xi $y0 $xi $yi] + if {$row <= $prevrow} { + puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow" + } + # see if the line extends the whole way from prevrow to row + if {$row > $prevrow + $uparrowlen + $downarrowlen && + [lsearch -exact [lindex $rowidlist \ + [expr {int(($row + $prevrow) / 2)}]] $id] < 0} { + # it doesn't, see where it ends + set r [expr {$prevrow + $downarrowlen}] + if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} { + while {[incr r -1] > $prevrow && + [lsearch -exact [lindex $rowidlist $r] $id] < 0} {} + } else { + while {[incr r] <= $row && + [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {} + incr r -1 + } + lappend ret $r + # see where it starts up again + set r [expr {$row - $uparrowlen}] + if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} { + while {[incr r] < $row && + [lsearch -exact [lindex $rowidlist $r] $id] < 0} {} + } else { + while {[incr r -1] >= $prevrow && + [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {} + incr r + } + lappend ret $r + } + } + if {$child eq $id} { + lappend ret $row } + set prev $id + set prevrow $row } - return $coords + return $ret } proc drawlineseg {id row endrow arrowlow} { global rowidlist displayorder iddrawn linesegs - global canv colormap linespc curview maxlinelen + global canv colormap linespc curview maxlinelen parentlist set cols [list [lsearch -exact [lindex $rowidlist $row] $id]] set le [expr {$row + 1}] @@ -3383,9 +3463,11 @@ proc drawlineseg {id row endrow arrowlow} { set itl [lindex $lines [expr {$i-1}] 2] set al [$canv itemcget $itl -arrow] set arrowlow [expr {$al eq "last" || $al eq "both"}] - } elseif {$arrowlow && - [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} { - set arrowlow 0 + } elseif {$arrowlow} { + if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 || + [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} { + set arrowlow 0 + } } set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]] for {set y $le} {[incr y -1] > $row} {} { @@ -3404,8 +3486,19 @@ proc drawlineseg {id row endrow arrowlow} { set xc [lsearch -exact [lindex $rowidlist $row] $ch] if {$xc < 0} { puts "oops: drawlineseg: child $ch not on row $row" - } else { - if {$xc < $x - 1} { + } elseif {$xc != $x} { + if {($arrowhigh && $le == $row + 1) || $dir == 0} { + set d [expr {int(0.5 * $linespc)}] + set x1 [xc $row $x] + if {$xc < $x} { + set x2 [expr {$x1 - $d}] + } else { + set x2 [expr {$x1 + $d}] + } + set y2 [yc $row] + set y1 [expr {$y2 + $d}] + lappend coords $x1 $y1 $x2 $y2 + } elseif {$xc < $x - 1} { lappend coords [xc $row [expr {$x-1}]] [yc $row] } elseif {$xc > $x + 1} { lappend coords [xc $row [expr {$x+1}]] [yc $row] @@ -3416,23 +3509,9 @@ proc drawlineseg {id row endrow arrowlow} { } else { set xn [xc $row $xp] set yn [yc $row] - # work around tk8.4 refusal to draw arrows on diagonal segments - if {$arrowlow && $xn != [lindex $coords end-1]} { - if {[llength $coords] < 4 || - [lindex $coords end-3] != [lindex $coords end-1] || - [lindex $coords end] - $yn > 2 * $linespc} { - set xn [xc $row [expr {$xp - 0.5 * $dir}]] - set yo [yc [expr {$row + 0.5}]] - lappend coords $xn $yo $xn $yn - } - } else { - lappend coords $xn $yn - } + lappend coords $xn $yn } if {!$joinhigh} { - if {$arrowhigh} { - set coords [adjarrowhigh $coords] - } assigncolor $id set t [$canv create line $coords -width [linewidth $id] \ -fill $colormap($id) -tags lines.$id -arrow $arrow] @@ -3456,9 +3535,6 @@ proc drawlineseg {id row endrow arrowlow} { set coords [concat $coords $clow] if {!$joinhigh} { lset lines [expr {$i-1}] 1 $le - if {$arrowhigh} { - set coords [adjarrowhigh $coords] - } } else { # coalesce two pieces $canv delete $ith @@ -3478,7 +3554,7 @@ proc drawlineseg {id row endrow arrowlow} { proc drawparentlinks {id row} { global rowidlist canv colormap curview parentlist - global idpos + global idpos linespc set rowids [lindex $rowidlist $row] set col [lsearch -exact $rowids $id] @@ -3488,6 +3564,8 @@ proc drawparentlinks {id row} { set x [xc $row $col] set y [yc $row] set y2 [yc $row2] + set d [expr {int(0.5 * $linespc)}] + set ymid [expr {$y + $d}] set ids [lindex $rowidlist $row2] # rmx = right-most X coord used set rmx 0 @@ -3501,19 +3579,37 @@ proc drawparentlinks {id row} { if {$x2 > $rmx} { set rmx $x2 } - if {[lsearch -exact $rowids $p] < 0} { + set j [lsearch -exact $rowids $p] + if {$j < 0} { # drawlineseg will do this one for us continue } assigncolor $p # should handle duplicated parents here... set coords [list $x $y] - if {$i < $col - 1} { - lappend coords [xc $row [expr {$i + 1}]] $y - } elseif {$i > $col + 1} { - lappend coords [xc $row [expr {$i - 1}]] $y + if {$i != $col} { + # if attaching to a vertical segment, draw a smaller + # slant for visual distinctness + if {$i == $j} { + if {$i < $col} { + lappend coords [expr {$x2 + $d}] $y $x2 $ymid + } else { + lappend coords [expr {$x2 - $d}] $y $x2 $ymid + } + } elseif {$i < $col && $i < $j} { + # segment slants towards us already + lappend coords [xc $row $j] $y + } else { + if {$i < $col - 1} { + lappend coords [expr {$x2 + $linespc}] $y + } elseif {$i > $col + 1} { + lappend coords [expr {$x2 - $linespc}] $y + } + lappend coords $x2 $y2 + } + } else { + lappend coords $x2 $y2 } - lappend coords $x2 $y2 set t [$canv create line $coords -width [linewidth $p] \ -fill $colormap($p) -tags lines.$p] $canv lower $t @@ -3535,8 +3631,8 @@ proc drawcmittext {id row col} { global linespc canv canv2 canv3 canvy0 fgcolor curview global commitlisted commitinfo rowidlist parentlist global rowtextx idpos idtags idheads idotherrefs - global linehtag linentag linedtag - global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2 + global linehtag linentag linedtag selectedline + global canvxmax boldrows boldnamerows fgcolor nullid nullid2 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right set listed [lindex $commitlisted $row] @@ -3593,15 +3689,15 @@ proc drawcmittext {id row col} { set name [lindex $commitinfo($id) 1] set date [lindex $commitinfo($id) 2] set date [formatdate $date] - set font $mainfont - set nfont $mainfont + set font mainfont + set nfont mainfont set isbold [ishighlighted $row] if {$isbold > 0} { lappend boldrows $row - lappend font bold + set font mainfontbold if {$isbold > 1} { lappend boldnamerows $row - lappend nfont bold + set nfont mainfontbold } } set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \ @@ -3610,8 +3706,11 @@ proc drawcmittext {id row col} { set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ -text $name -font $nfont -tags text] set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ - -text $date -font $mainfont -tags text] - set xr [expr {$xt + [font measure $mainfont $headline]}] + -text $date -font mainfont -tags text] + if {[info exists selectedline] && $selectedline == $row} { + make_secsel $row + } + set xr [expr {$xt + [font measure $font $headline]}] if {$xr > $canvxmax} { set canvxmax $xr setcanvscroll @@ -3619,10 +3718,10 @@ proc drawcmittext {id row col} { } proc drawcmitrow {row} { - global displayorder rowidlist + global displayorder rowidlist nrows_drawn global iddrawn markingmatches global commitinfo parentlist numcommits - global filehighlight fhighlights findstring nhighlights + global filehighlight fhighlights findpattern nhighlights global hlview vhighlights global highlight_related rhighlights @@ -3635,7 +3734,7 @@ proc drawcmitrow {row} { if {[info exists filehighlight] && ![info exists fhighlights($row)]} { askfilehighlight $row $id } - if {$findstring ne {} && ![info exists nhighlights($row)]} { + if {$findpattern ne {} && ![info exists nhighlights($row)]} { askfindhighlight $row $id } if {$highlight_related ne "None" && ![info exists rhighlights($row)]} { @@ -3653,6 +3752,7 @@ proc drawcmitrow {row} { assigncolor $id drawcmittext $id $row $col set iddrawn($id) 1 + incr nrows_drawn } if {$markingmatches} { markrowmatches $row $id @@ -3660,8 +3760,8 @@ proc drawcmitrow {row} { } proc drawcommits {row {endrow {}}} { - global numcommits iddrawn displayorder curview - global parentlist rowidlist + global numcommits iddrawn displayorder curview need_redisplay + global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn if {$row < 0} { set row 0 @@ -3673,6 +3773,35 @@ proc drawcommits {row {endrow {}}} { set endrow [expr {$numcommits - 1}] } + set rl1 [expr {$row - $downarrowlen - 3}] + if {$rl1 < 0} { + set rl1 0 + } + set ro1 [expr {$row - 3}] + if {$ro1 < 0} { + set ro1 0 + } + set r2 [expr {$endrow + $uparrowlen + 3}] + if {$r2 > $numcommits} { + set r2 $numcommits + } + for {set r $rl1} {$r < $r2} {incr r} { + if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} { + if {$rl1 < $r} { + layoutrows $rl1 $r + } + set rl1 [expr {$r + 1}] + } + } + if {$rl1 < $r} { + layoutrows $rl1 $r + } + optimize_rows $ro1 0 $r2 + if {$need_redisplay || $nrows_drawn > 2000} { + clear_display + drawvisible + } + # make the lines join to already-drawn rows either side set r [expr {$row - 1}] if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} { @@ -3689,34 +3818,23 @@ proc drawcommits {row {endrow {}}} { drawcmitrow $r if {$r == $er} break set nextid [lindex $displayorder [expr {$r + 1}]] - if {$wasdrawn && [info exists iddrawn($nextid)]} { - catch {unset prevlines} - continue - } + if {$wasdrawn && [info exists iddrawn($nextid)]} continue drawparentlinks $id $r - if {[info exists lineends($r)]} { - foreach lid $lineends($r) { - unset prevlines($lid) - } - } set rowids [lindex $rowidlist $r] foreach lid $rowids { if {$lid eq {}} continue + if {[info exists lineend($lid)] && $lineend($lid) > $r} continue if {$lid eq $id} { # see if this is the first child of any of its parents foreach p [lindex $parentlist $r] { if {[lsearch -exact $rowids $p] < 0} { # make this line extend up to the child - set le [drawlineseg $p $r $er 0] - lappend lineends($le) $p - set prevlines($p) 1 + set lineend($p) [drawlineseg $p $r $er 0] } } - } elseif {![info exists prevlines($lid)]} { - set le [drawlineseg $lid $r $er 1] - lappend lineends($le) $lid - set prevlines($lid) 1 + } else { + set lineend($lid) [drawlineseg $lid $r $er 1] } } } @@ -3740,7 +3858,7 @@ proc drawvisible {} { } proc clear_display {} { - global iddrawn linesegs + global iddrawn linesegs need_redisplay nrows_drawn global vhighlights fhighlights nhighlights rhighlights allcanvs delete all @@ -3750,10 +3868,12 @@ proc clear_display {} { catch {unset fhighlights} catch {unset nhighlights} catch {unset rhighlights} + set need_redisplay 0 + set nrows_drawn 0 } proc findcrossings {id} { - global rowidlist parentlist numcommits rowoffsets displayorder + global rowidlist parentlist numcommits displayorder set cross {} set ccross {} @@ -3762,12 +3882,9 @@ proc findcrossings {id} { set e [expr {$numcommits - 1}] } if {$e <= $s} continue - set x [lsearch -exact [lindex $rowidlist $e] $id] - if {$x < 0} { - puts "findcrossings: oops, no [shortids $id] in row $e" - continue - } for {set row $e} {[incr row -1] >= $s} {} { + set x [lsearch -exact [lindex $rowidlist $row] $id] + if {$x < 0} break set olds [lindex $parentlist $row] set kid [lindex $displayorder $row] set kidx [lsearch -exact [lindex $rowidlist $row] $kid] @@ -3785,9 +3902,6 @@ proc findcrossings {id} { } } } - set inc [lindex $rowoffsets $row $x] - if {$inc eq {}} break - incr x $inc } } return [concat $ccross {{}} $cross] @@ -3868,7 +3982,7 @@ proc bindline {t id} { proc drawtags {id x xt y1} { global idtags idheads idotherrefs mainhead global linespc lthickness - global canv mainfont commitrow rowtextx curview fgcolor bgcolor + global canv commitrow rowtextx curview fgcolor bgcolor set marks {} set ntags 0 @@ -3897,9 +4011,9 @@ proc drawtags {id x xt y1} { foreach tag $marks { incr i if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} { - set wid [font measure [concat $mainfont bold] $tag] + set wid [font measure mainfontbold $tag] } else { - set wid [font measure $mainfont $tag] + set wid [font measure mainfont $tag] } lappend xvals $xt lappend wvals $wid @@ -3911,7 +4025,7 @@ proc drawtags {id x xt y1} { foreach tag $marks x $xvals wid $wvals { set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] - set font $mainfont + set font mainfont if {[incr ntags -1] >= 0} { # draw a tag set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ @@ -3924,7 +4038,7 @@ proc drawtags {id x xt y1} { if {[incr nheads -1] >= 0} { set col green if {$tag eq $mainhead} { - lappend font bold + set font mainfontbold } } else { set col "#ddddff" @@ -3933,7 +4047,7 @@ proc drawtags {id x xt y1} { $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ -width 1 -outline black -fill $col -tags tag.$id if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} { - set rwid [font measure $mainfont $remoteprefix] + set rwid [font measure mainfont $remoteprefix] set xi [expr {$x + 1}] set yti [expr {$yt + 1}] set xri [expr {$x + $rwid}] @@ -3965,10 +4079,10 @@ proc xcoord {i level ln} { } proc show_status {msg} { - global canv mainfont fgcolor + global canv fgcolor clear_display - $canv create text 3 3 -anchor nw -text $msg -font $mainfont \ + $canv create text 3 3 -anchor nw -text $msg -font mainfont \ -tags text -fill $fgcolor } @@ -3977,9 +4091,9 @@ proc show_status {msg} { # on that row and below will move down one row. proc insertrow {row newcmit} { global displayorder parentlist commitlisted children - global commitrow curview rowidlist rowoffsets numcommits - global rowrangelist rowlaidout rowoptim numcommits - global selectedline rowchk commitidx + global commitrow curview rowidlist rowisopt rowfinal numcommits + global numcommits + global selectedline commitidx ordertok if {$row >= $numcommits} { puts "oops, inserting new row $row but only have $numcommits rows" @@ -3999,45 +4113,24 @@ proc insertrow {row newcmit} { set commitrow($curview,$id) $r } incr commitidx($curview) + set ordertok($curview,$newcmit) $ordertok($curview,$p) - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] - set newoffs {} - foreach x $idlist { - if {$x eq {} || ($x eq $p && [llength $kids] == 1)} { - lappend newoffs {} - } else { - lappend newoffs 0 - } - } - if {[llength $kids] == 1} { - set col [lsearch -exact $idlist $p] - lset idlist $col $newcmit - } else { - set col [llength $idlist] - lappend idlist $newcmit - lappend offs {} - lset rowoffsets $row $offs - } - set rowidlist [linsert $rowidlist $row $idlist] - set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs] - - set rowrangelist [linsert $rowrangelist $row {}] - if {[llength $kids] > 1} { - set rp1 [expr {$row + 1}] - set ranges [lindex $rowrangelist $rp1] - if {$ranges eq {}} { - set ranges [list $newcmit $p] - } elseif {[lindex $ranges end-1] eq $p} { - lset ranges end-1 $newcmit + if {$row < [llength $rowidlist]} { + set idlist [lindex $rowidlist $row] + if {$idlist ne {}} { + if {[llength $kids] == 1} { + set col [lsearch -exact $idlist $p] + lset idlist $col $newcmit + } else { + set col [llength $idlist] + lappend idlist $newcmit + } } - lset rowrangelist $rp1 $ranges + set rowidlist [linsert $rowidlist $row $idlist] + set rowisopt [linsert $rowisopt $row 0] + set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]] } - catch {unset rowchk} - - incr rowlaidout - incr rowoptim incr numcommits if {[info exists selectedline] && $selectedline >= $row} { @@ -4049,9 +4142,9 @@ proc insertrow {row newcmit} { # Remove a commit that was inserted with insertrow on row $row. proc removerow {row} { global displayorder parentlist commitlisted children - global commitrow curview rowidlist rowoffsets numcommits - global rowrangelist idrowranges rowlaidout rowoptim numcommits - global linesegends selectedline rowchk commitidx + global commitrow curview rowidlist rowisopt rowfinal numcommits + global numcommits + global linesegends selectedline commitidx if {$row >= $numcommits} { puts "oops, removing row $row but only have $numcommits rows" @@ -4076,27 +4169,12 @@ proc removerow {row} { } incr commitidx($curview) -1 - set rowidlist [lreplace $rowidlist $row $row] - set rowoffsets [lreplace $rowoffsets $rp1 $rp1] - if {$kids ne {}} { - set offs [lindex $rowoffsets $row] - set offs [lreplace $offs end end] - lset rowoffsets $row $offs - } - - set rowrangelist [lreplace $rowrangelist $row $row] - if {[llength $kids] > 0} { - set ranges [lindex $rowrangelist $row] - if {[lindex $ranges end-1] eq $id} { - set ranges [lreplace $ranges end-1 end] - lset rowrangelist $row $ranges - } + if {$row < [llength $rowidlist]} { + set rowidlist [lreplace $rowidlist $row $row] + set rowisopt [lreplace $rowisopt $row $row] + set rowfinal [lreplace $rowfinal $row $row] } - catch {unset rowchk} - - incr rowlaidout -1 - incr rowoptim -1 incr numcommits -1 if {[info exists selectedline] && $selectedline > $row} { @@ -4116,20 +4194,30 @@ proc settextcursor {c} { set curtextcursor $c } -proc nowbusy {what} { - global isbusy +proc nowbusy {what {name {}}} { + global isbusy busyname statusw if {[array names isbusy] eq {}} { . config -cursor watch settextcursor watch } set isbusy($what) 1 + set busyname($what) $name + if {$name ne {}} { + $statusw conf -text $name + } } proc notbusy {what} { - global isbusy maincursor textcursor + global isbusy maincursor textcursor busyname statusw - catch {unset isbusy($what)} + catch { + unset isbusy($what) + if {$busyname($what) ne {} && + [$statusw cget -text] eq $busyname($what)} { + $statusw conf -text {} + } + } if {[array names isbusy] eq {}} { . config -cursor $maincursor settextcursor $textcursor @@ -4157,148 +4245,149 @@ proc findmatches {f} { return $matches } -proc dofind {{rev 0}} { +proc dofind {{dirn 1} {wrap 1}} { global findstring findstartline findcurline selectedline numcommits + global gdttype filehighlight fh_serial find_dirn findallowwrap - unmarkmatches - cancel_next_highlight + if {[info exists find_dirn]} { + if {$find_dirn == $dirn} return + stopfinding + } focus . if {$findstring eq {} || $numcommits == 0} return if {![info exists selectedline]} { - set findstartline [lindex [visiblerows] $rev] + set findstartline [lindex [visiblerows] [expr {$dirn < 0}]] } else { set findstartline $selectedline } set findcurline $findstartline - nowbusy finding - if {!$rev} { - run findmore - } else { - if {$findcurline == 0} { - set findcurline $numcommits - } - incr findcurline -1 - run findmorerev + nowbusy finding "Searching" + if {$gdttype ne "containing:" && ![info exists filehighlight]} { + after cancel do_file_hl $fh_serial + do_file_hl $fh_serial } + set find_dirn $dirn + set findallowwrap $wrap + run findmore } -proc findnext {restart} { - global findcurline - if {![info exists findcurline]} { - if {$restart} { - dofind - } else { - bell - } - } else { - run findmore - nowbusy finding - } -} +proc stopfinding {} { + global find_dirn findcurline fprogcoord -proc findprev {} { - global findcurline - if {![info exists findcurline]} { - dofind 1 - } else { - run findmorerev - nowbusy finding + if {[info exists find_dirn]} { + unset find_dirn + unset findcurline + notbusy finding + set fprogcoord 0 + adjustprogress } } proc findmore {} { - global commitdata commitinfo numcommits findstring findpattern findloc + global commitdata commitinfo numcommits findpattern findloc global findstartline findcurline displayorder + global find_dirn gdttype fhighlights fprogcoord + global findallowwrap - set fldtypes {Headline Author Date Committer CDate Comments} - set l [expr {$findcurline + 1}] - if {$l >= $numcommits} { - set l 0 - } - if {$l <= $findstartline} { - set lim [expr {$findstartline + 1}] - } else { - set lim $numcommits - } - if {$lim - $l > 500} { - set lim [expr {$l + 500}] - } - set last 0 - for {} {$l < $lim} {incr l} { - set id [lindex $displayorder $l] - # shouldn't happen unless git log doesn't give all the commits... - if {![info exists commitdata($id)]} continue - if {![doesmatch $commitdata($id)]} continue - if {![info exists commitinfo($id)]} { - getcommit $id - } - set info $commitinfo($id) - foreach f $info ty $fldtypes { - if {($findloc eq "All fields" || $findloc eq $ty) && - [doesmatch $f]} { - findselectline $l - notbusy finding - return 0 - } - } - } - if {$l == $findstartline + 1} { - bell - unset findcurline - notbusy finding + if {![info exists find_dirn]} { return 0 } - set findcurline [expr {$l - 1}] - return 1 -} - -proc findmorerev {} { - global commitdata commitinfo numcommits findstring findpattern findloc - global findstartline findcurline displayorder - set fldtypes {Headline Author Date Committer CDate Comments} set l $findcurline - if {$l == 0} { - set l $numcommits - } - incr l -1 - if {$l >= $findstartline} { - set lim [expr {$findstartline - 1}] + set moretodo 0 + if {$find_dirn > 0} { + incr l + if {$l >= $numcommits} { + set l 0 + } + if {$l <= $findstartline} { + set lim [expr {$findstartline + 1}] + } else { + set lim $numcommits + set moretodo $findallowwrap + } } else { - set lim -1 - } - if {$l - $lim > 500} { - set lim [expr {$l - 500}] - } - set last 0 - for {} {$l > $lim} {incr l -1} { - set id [lindex $displayorder $l] - if {![doesmatch $commitdata($id)]} continue - if {![info exists commitinfo($id)]} { - getcommit $id + if {$l == 0} { + set l $numcommits + } + incr l -1 + if {$l >= $findstartline} { + set lim [expr {$findstartline - 1}] + } else { + set lim -1 + set moretodo $findallowwrap + } + } + set n [expr {($lim - $l) * $find_dirn}] + if {$n > 500} { + set n 500 + set moretodo 1 + } + set found 0 + set domore 1 + if {$gdttype eq "containing:"} { + for {} {$n > 0} {incr n -1; incr l $find_dirn} { + set id [lindex $displayorder $l] + # shouldn't happen unless git log doesn't give all the commits... + if {![info exists commitdata($id)]} continue + if {![doesmatch $commitdata($id)]} continue + if {![info exists commitinfo($id)]} { + getcommit $id + } + set info $commitinfo($id) + foreach f $info ty $fldtypes { + if {($findloc eq "All fields" || $findloc eq $ty) && + [doesmatch $f]} { + set found 1 + break + } + } + if {$found} break } - set info $commitinfo($id) - foreach f $info ty $fldtypes { - if {($findloc eq "All fields" || $findloc eq $ty) && - [doesmatch $f]} { - findselectline $l - notbusy finding - return 0 + } else { + for {} {$n > 0} {incr n -1; incr l $find_dirn} { + set id [lindex $displayorder $l] + if {![info exists fhighlights($l)]} { + askfilehighlight $l $id + if {$domore} { + set domore 0 + set findcurline [expr {$l - $find_dirn}] + } + } elseif {$fhighlights($l)} { + set found $domore + break } } } - if {$l == -1} { - bell + if {$found || ($domore && !$moretodo)} { unset findcurline + unset find_dirn notbusy finding + set fprogcoord 0 + adjustprogress + if {$found} { + findselectline $l + } else { + bell + } return 0 } - set findcurline [expr {$l + 1}] - return 1 + if {!$domore} { + flushhighlights + } else { + set findcurline [expr {$l - $find_dirn}] + } + set n [expr {($findcurline - $findstartline) * $find_dirn - 1}] + if {$n < 0} { + incr n $numcommits + } + set fprogcoord [expr {$n * 1.0 / $numcommits}] + adjustprogress + return $domore } proc findselectline {l} { - global findloc commentend ctext findcurline markingmatches + global findloc commentend ctext findcurline markingmatches gdttype set markingmatches 1 set findcurline $l @@ -4341,12 +4430,11 @@ proc markmatches {canv l str tag matches font row} { } proc unmarkmatches {} { - global findids markingmatches findcurline + global markingmatches allcanvs delete matches - catch {unset findids} set markingmatches 0 - catch {unset findcurline} + stopfinding } proc selcanvline {w x y} { @@ -4382,7 +4470,7 @@ proc commit_descriptor {p} { # append some text to the ctext widget, and make any SHA1 ID # that we know about be a clickable link. proc appendwithlinks {text tags} { - global ctext commitrow linknum curview + global ctext commitrow linknum curview pendinglinks set start [$ctext index "end - 1c"] $ctext insert end $text $tags @@ -4391,17 +4479,49 @@ proc appendwithlinks {text tags} { set s [lindex $l 0] set e [lindex $l 1] set linkid [string range $text $s $e] - if {![info exists commitrow($curview,$linkid)]} continue incr e - $ctext tag add link "$start + $s c" "$start + $e c" + $ctext tag delete link$linknum $ctext tag add link$linknum "$start + $s c" "$start + $e c" - $ctext tag bind link$linknum <1> \ - [list selectline $commitrow($curview,$linkid) 1] + setlink $linkid link$linknum incr linknum } - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } +} + +proc setlink {id lk} { + global curview commitrow ctext pendinglinks commitinterest + + if {[info exists commitrow($curview,$id)]} { + $ctext tag conf $lk -foreground blue -underline 1 + $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1] + $ctext tag bind $lk <Enter> {linkcursor %W 1} + $ctext tag bind $lk <Leave> {linkcursor %W -1} + } else { + lappend pendinglinks($id) $lk + lappend commitinterest($id) {makelink %I} + } +} + +proc makelink {id} { + global pendinglinks + + if {![info exists pendinglinks($id)]} return + foreach lk $pendinglinks($id) { + setlink $id $lk + } + unset pendinglinks($id) +} + +proc linkcursor {w inc} { + global linkentercount curtextcursor + + if {[incr linkentercount $inc] > 0} { + $w configure -cursor hand2 + } else { + $w configure -cursor $curtextcursor + if {$linkentercount < 0} { + set linkentercount 0 + } + } } proc viewnextline {dir} { @@ -4448,15 +4568,7 @@ proc appendrefs {pos ids var} { $ctext tag delete $lk $ctext insert $pos $sep $ctext insert $pos [lindex $ti 0] $lk - if {[info exists commitrow($curview,$id)]} { - $ctext tag conf $lk -foreground blue - $ctext tag bind $lk <1> \ - [list selectline $commitrow($curview,$id) 1] - $ctext tag conf $lk -underline 1 - $ctext tag bind $lk <Enter> { %W configure -cursor hand2 } - $ctext tag bind $lk <Leave> \ - { %W configure -cursor $curtextcursor } - } + setlink $id $lk set sep ", " } } @@ -4514,9 +4626,27 @@ proc dispnexttag {} { } } +proc make_secsel {l} { + global linehtag linentag linedtag canv canv2 canv3 + + if {![info exists linehtag($l)]} return + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + -tags secsel -fill [$canv cget -selectbackground]] + $canv lower $t + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + -tags secsel -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + $canv3 delete secsel + set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + -tags secsel -fill [$canv3 cget -selectbackground]] + $canv3 lower $t +} + proc selectline {l isnew} { - global canv canv2 canv3 ctext commitinfo selectedline - global displayorder linehtag linentag linedtag + global canv ctext commitinfo selectedline + global displayorder global canvy0 linespc parentlist children curview global currentid sha1entry global commentend idtags linknum @@ -4526,8 +4656,8 @@ proc selectline {l isnew} { catch {unset pending_select} $canv delete hover normalline - cancel_next_highlight unsel_reflist + stopfinding if {$l < 0 || $l >= $numcommits} return set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] @@ -4565,19 +4695,7 @@ proc selectline {l isnew} { drawvisible } - if {![info exists linehtag($l)]} return - $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ - -tags secsel -fill [$canv cget -selectbackground]] - $canv lower $t - $canv2 delete secsel - set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ - -tags secsel -fill [$canv2 cget -selectbackground]] - $canv2 lower $t - $canv3 delete secsel - set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ - -tags secsel -fill [$canv3 cget -selectbackground]] - $canv3 lower $t + make_secsel $l if {$isnew} { addtohistory [list selectline $l 0] @@ -4720,7 +4838,6 @@ proc unselectline {} { catch {unset currentid} allcanvs delete secsel rhighlight_none - cancel_next_highlight } proc reselectline {} { @@ -4889,6 +5006,7 @@ proc showfile {f} { $ctext insert end "$f\n" filesep $ctext config -state disabled $ctext yview $commentend + settabs 0 } proc getblobline {bf id} { @@ -4914,15 +5032,18 @@ proc getblobline {bf id} { } proc mergediff {id l} { - global diffmergeid diffopts mdifffd + global diffmergeid mdifffd global diffids global parentlist + global limitdiffs viewfiles curview set diffmergeid $id set diffids $id # this doesn't seem to actually affect anything... - set env(GIT_DIFF_OPTS) $diffopts set cmd [concat | git diff-tree --no-commit-id --cc $id] + if {$limitdiffs && $viewfiles($curview) ne {}} { + set cmd [concat $cmd -- $viewfiles($curview)] + } if {[catch {set mdf [open $cmd r]} err]} { error_popup "Error getting merge diffs: $err" return @@ -4930,6 +5051,7 @@ proc mergediff {id l} { fconfigure $mdf -blocking 0 set mdifffd($id) $mdf set np [llength [lindex $parentlist $l]] + settabs $np filerun $mdf [list getmergediffline $mdf $id $np] } @@ -5007,6 +5129,7 @@ proc getmergediffline {mdf id np} { proc startdiff {ids} { global treediffs diffids treepending diffmergeid nullid nullid2 + settabs 1 set diffids $ids catch {unset diffmergeid} if {![info exists treediffs($ids)] || @@ -5020,8 +5143,27 @@ proc startdiff {ids} { } } +proc path_filter {filter name} { + foreach p $filter { + set l [string length $p] + if {[string index $p end] eq "/"} { + if {[string compare -length $l $p $name] == 0} { + return 1 + } + } else { + if {[string compare -length $l $p $name] == 0 && + ([string length $name] == $l || + [string index $name $l] eq "/")} { + return 1 + } + } + } + return 0 +} + proc addtocflist {ids} { - global treediffs cflist + global treediffs + add_flist $treediffs($ids) getblobdiffs $ids } @@ -5078,7 +5220,7 @@ proc gettreediffs {ids} { proc gettreediffline {gdtf ids} { global treediff treediffs treepending diffids diffmergeid - global cmitmode + global cmitmode viewfiles curview limitdiffs set nr 0 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} { @@ -5095,7 +5237,17 @@ proc gettreediffline {gdtf ids} { return [expr {$nr >= 1000? 2: 1}] } close $gdtf - set treediffs($ids) $treediff + if {$limitdiffs && $viewfiles($curview) ne {}} { + set flist {} + foreach f $treediff { + if {[path_filter $viewfiles($curview) $f]} { + lappend flist $f + } + } + set treediffs($ids) $flist + } else { + set treediffs($ids) $treediff + } unset treepending if {$cmitmode eq "tree"} { gettree $diffids @@ -5126,12 +5278,16 @@ proc diffcontextchange {n1 n2 op} { } proc getblobdiffs {ids} { - global diffopts blobdifffd diffids env + global blobdifffd diffids env global diffinhdr treediffs global diffcontext + global limitdiffs viewfiles curview - set env(GIT_DIFF_OPTS) $diffopts - if {[catch {set bdf [open [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] r]} err]} { + set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] + if {$limitdiffs && $viewfiles($curview) ne {}} { + set cmd [concat $cmd -- $viewfiles($curview)] + } + if {[catch {set bdf [open $cmd r]} err]} { puts "error getting diffs: $err" return } @@ -5215,8 +5371,7 @@ proc getblobdiffline {bdf ids} { set diffinhdr 0 } elseif {$diffinhdr} { - if {![string compare -length 12 "rename from " $line] || - ![string compare -length 10 "copy from " $line]} { + if {![string compare -length 12 "rename from " $line]} { set fname [string range $line [expr 6 + [string first " from " $line] ] end] if {[string index $fname 0] eq "\""} { set fname [lindex $fname 0] @@ -5297,6 +5452,7 @@ proc nextfile {} { proc clear_ctext {{first 1.0}} { global ctext smarktop smarkbot + global pendinglinks set l [lindex [split $first .] 0] if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} { @@ -5306,6 +5462,26 @@ proc clear_ctext {{first 1.0}} { set smarkbot $l } $ctext delete $first end + if {$first eq "1.0"} { + catch {unset pendinglinks} + } +} + +proc settabs {{firstab {}}} { + global firsttabstop tabstop ctext have_tk85 + + if {$firstab ne {} && $have_tk85} { + set firsttabstop $firstab + } + set w [font measure textfont "0"] + if {$firsttabstop != 0} { + $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \ + [expr {($firsttabstop + 2 * $tabstop) * $w}]] + } elseif {$have_tk85 || $tabstop != 8} { + $ctext conf -tabs [expr {$tabstop * $w}] + } else { + $ctext conf -tabs {} + } } proc incrsearch {name ix op} { @@ -5428,11 +5604,11 @@ proc scrolltext {f0 f1} { } proc setcoords {} { - global linespc charspc canvx0 canvy0 mainfont + global linespc charspc canvx0 canvy0 global xspc1 xspc2 lthickness - set linespc [font metrics $mainfont -linespace] - set charspc [font measure $mainfont "m"] + set linespc [font metrics mainfont -linespace] + set charspc [font measure mainfont "m"] set canvy0 [expr {int(3 + 0.5 * $linespc)}] set canvx0 [expr {int(3 + 0.5 * $linespc)}] set lthickness [expr {int($linespc / 9) + 1}] @@ -5457,26 +5633,75 @@ proc redisplay {} { } } +proc parsefont {f n} { + global fontattr + + set fontattr($f,family) [lindex $n 0] + set s [lindex $n 1] + if {$s eq {} || $s == 0} { + set s 10 + } elseif {$s < 0} { + set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}] + } + set fontattr($f,size) $s + set fontattr($f,weight) normal + set fontattr($f,slant) roman + foreach style [lrange $n 2 end] { + switch -- $style { + "normal" - + "bold" {set fontattr($f,weight) $style} + "roman" - + "italic" {set fontattr($f,slant) $style} + } + } +} + +proc fontflags {f {isbold 0}} { + global fontattr + + return [list -family $fontattr($f,family) -size $fontattr($f,size) \ + -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \ + -slant $fontattr($f,slant)] +} + +proc fontname {f} { + global fontattr + + set n [list $fontattr($f,family) $fontattr($f,size)] + if {$fontattr($f,weight) eq "bold"} { + lappend n "bold" + } + if {$fontattr($f,slant) eq "italic"} { + lappend n "italic" + } + return $n +} + proc incrfont {inc} { global mainfont textfont ctext canv phase cflist showrefstop - global charspc tabstop - global stopped entries + global stopped entries fontattr + unmarkmatches - set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]] - set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]] + set s $fontattr(mainfont,size) + incr s $inc + if {$s < 1} { + set s 1 + } + set fontattr(mainfont,size) $s + font config mainfont -size $s + font config mainfontbold -size $s + set mainfont [fontname mainfont] + set s $fontattr(textfont,size) + incr s $inc + if {$s < 1} { + set s 1 + } + set fontattr(textfont,size) $s + font config textfont -size $s + font config textfontbold -size $s + set textfont [fontname textfont] setcoords - $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]" - $cflist conf -font $textfont - $ctext tag conf filesep -font [concat $textfont bold] - foreach e $entries { - $e conf -font $mainfont - } - if {$phase eq "getcommits"} { - $canv itemconf textitems -font $mainfont - } - if {[info exists showrefstop] && [winfo exists $showrefstop]} { - $showrefstop.list conf -font $mainfont - } + settabs redisplay } @@ -5587,7 +5812,7 @@ proc lineleave {id} { proc linehover {} { global hoverx hovery hoverid hovertimer global canv linespc lthickness - global commitinfo mainfont + global commitinfo set text [lindex $commitinfo($hoverid) 0] set ymax [lindex [$canv cget -scrollregion] 3] @@ -5597,13 +5822,13 @@ proc linehover {} { set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}] set x0 [expr {$x - 2 * $lthickness}] set y0 [expr {$y - 2 * $lthickness}] - set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}] + set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}] set y1 [expr {$y + $linespc + 2 * $lthickness}] set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t set t [$canv create text $x $y -anchor nw -text $text -tags hover \ - -font $mainfont] + -font mainfont] $canv raise $t } @@ -5641,7 +5866,7 @@ proc arrowjump {id n y} { } proc lineclick {x y id isnew} { - global ctext commitinfo children canv thickerline curview + global ctext commitinfo children canv thickerline curview commitrow if {![info exists commitinfo($id)] && ![getcommit $id]} return unmarkmatches @@ -5669,12 +5894,10 @@ proc lineclick {x y id isnew} { # fill the details pane with info about this line $ctext conf -state normal clear_ctext - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + settabs 0 $ctext insert end "Parent:\t" - $ctext insert end $id [list link link0] - $ctext tag bind link0 <1> [list selbyid $id] + $ctext insert end $id link0 + setlink $id link0 set info $commitinfo($id) $ctext insert end "\n\t[lindex $info 0]\n" $ctext insert end "\tAuthor:\t[lindex $info 1]\n" @@ -5689,8 +5912,8 @@ proc lineclick {x y id isnew} { if {![info exists commitinfo($child)] && ![getcommit $child]} continue set info $commitinfo($child) $ctext insert end "\n\t" - $ctext insert end $child [list link link$i] - $ctext tag bind link$i <1> [list selbyid $child] + $ctext insert end $child link$i + setlink $child link$i $ctext insert end "\n\t[lindex $info 0]" $ctext insert end "\n\tAuthor:\t[lindex $info 1]" set date [formatdate [lindex $info 2]] @@ -5729,6 +5952,7 @@ proc rowmenu {x y id} { global rowctxmenu commitrow selectedline rowmenuid curview global nullid nullid2 fakerowmenu mainhead + stopfinding set rowmenuid $id if {![info exists selectedline] || $commitrow($curview,$id) eq $selectedline} { @@ -5771,16 +5995,13 @@ proc doseldiff {oldid newid} { clear_ctext init_flist "Top" $ctext insert end "From " - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } - $ctext tag bind link0 <1> [list selbyid $oldid] - $ctext insert end $oldid [list link link0] + $ctext insert end $oldid link0 + setlink $oldid link0 $ctext insert end "\n " $ctext insert end [lindex $commitinfo($oldid) 0] $ctext insert end "\n\nTo " - $ctext tag bind link1 <1> [list selbyid $newid] - $ctext insert end $newid [list link link1] + $ctext insert end $newid link1 + setlink $newid link1 $ctext insert end "\n " $ctext insert end [lindex $commitinfo($newid) 0] $ctext insert end "\n" @@ -5861,6 +6082,8 @@ proc mkpatchgo {} { set newid [$patchtop.tosha1 get] set fname [$patchtop.fname get] set cmd [diffcmd [list $oldid $newid] -p] + # trim off the initial "|" + set cmd [lrange $cmd 1 end] lappend cmd >$fname & if {[catch {eval exec $cmd} err]} { error_popup "Error creating patch: $err" @@ -5941,7 +6164,7 @@ proc domktag {} { proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview - global mainfont canvxmax iddrawn + global canvxmax iddrawn if {![info exists commitrow($curview,$id)]} return if {![info exists iddrawn($id)]} return @@ -5950,7 +6173,7 @@ proc redrawtags {id} { set xt [eval drawtags $id $idpos($id)] $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2] set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text] - set xr [expr {$xt + [font measure $mainfont $text]}] + set xr [expr {$xt + [font measure mainfont $text]}] if {$xr > $canvxmax} { set canvxmax $xr setcanvscroll @@ -6093,7 +6316,7 @@ proc cherrypick {} { included in branch $mainhead -- really re-apply it?"] if {!$ok} return } - nowbusy cherrypick + nowbusy cherrypick "Cherry-picking" update # Unfortunately git-cherry-pick writes stuff to stderr even when # no error occurs, and exec takes that as an indication of error... @@ -6123,7 +6346,6 @@ proc cherrypick {} { proc resethead {} { global mainheadid mainhead rowmenuid confirm_ok resettype - global showlocalchanges set confirm_ok 0 set w ".confirmreset" @@ -6160,32 +6382,23 @@ proc resethead {} { error_popup $err } else { dohidelocalchanges - set w ".resetprogress" - filerun $fd [list readresetstat $fd $w] - toplevel $w - wm transient $w - wm title $w "Reset progress" - message $w.m -text "Reset in progress, please wait..." \ - -justify center -aspect 1000 - pack $w.m -side top -fill x -padx 20 -pady 5 - canvas $w.c -width 150 -height 20 -bg white - $w.c create rect 0 0 0 20 -fill green -tags rect - pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1 - nowbusy reset + filerun $fd [list readresetstat $fd] + nowbusy reset "Resetting" } } -proc readresetstat {fd w} { - global mainhead mainheadid showlocalchanges +proc readresetstat {fd} { + global mainhead mainheadid showlocalchanges rprogcoord if {[gets $fd line] >= 0} { if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} { - set x [expr {($m * 150) / $n}] - $w.c coords rect 0 0 $x 20 + set rprogcoord [expr {1.0 * $m / $n}] + adjustprogress } return 1 } - destroy $w + set rprogcoord 0 + adjustprogress notbusy reset if {[catch {close $fd} err]} { error_popup $err @@ -6209,6 +6422,7 @@ proc readresetstat {fd w} { proc headmenu {x y id head} { global headmenuid headmenuhead headctxmenu mainhead + stopfinding set headmenuid $id set headmenuhead $head set state normal @@ -6226,7 +6440,7 @@ proc cobranch {} { # check the tree is clean first?? set oldmainhead $mainhead - nowbusy checkout + nowbusy checkout "Checking out" update dohidelocalchanges if {[catch { @@ -6282,8 +6496,8 @@ proc rmbranch {} { # Display a list of tags and heads proc showrefs {} { - global showrefstop bgcolor fgcolor selectbgcolor mainfont - global bglist fglist uifont reflistfilter reflist maincursor + global showrefstop bgcolor fgcolor selectbgcolor + global bglist fglist reflistfilter reflist maincursor set top .showrefs set showrefstop $top @@ -6295,7 +6509,7 @@ proc showrefs {} { toplevel $top wm title $top "Tags and heads: [file tail [pwd]]" text $top.list -background $bgcolor -foreground $fgcolor \ - -selectbackground $selectbgcolor -font $mainfont \ + -selectbackground $selectbgcolor -font mainfont \ -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \ -width 30 -height 20 -cursor $maincursor \ -spacing1 1 -spacing3 1 -state disabled @@ -6307,15 +6521,15 @@ proc showrefs {} { grid $top.list $top.ysb -sticky nsew grid $top.xsb x -sticky ew frame $top.f - label $top.f.l -text "Filter: " -font $uifont - entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont + label $top.f.l -text "Filter: " -font uifont + entry $top.f.e -width 20 -textvariable reflistfilter -font uifont set reflistfilter "*" trace add variable reflistfilter write reflistfilter_change pack $top.f.e -side right -fill x -expand 1 pack $top.f.l -side left grid $top.f - -sticky ew -pady 2 button $top.close -command [list destroy $top] -text "Close" \ - -font $uifont + -font uifont grid $top.close - grid columnconfigure $top 0 -weight 1 grid rowconfigure $top 0 -weight 1 @@ -6438,25 +6652,59 @@ proc refill_reflist {} { # Stuff for finding nearby tags proc getallcommits {} { - global allcommits allids nbmp nextarc seeds + global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate + global idheads idtags idotherrefs allparents tagobjid if {![info exists allcommits]} { - set allids {} - set nbmp 0 set nextarc 0 set allcommits 0 set seeds {} + set allcwait 0 + set cachedarcs 0 + set allccache [file join [gitdir] "gitk.cache"] + if {![catch { + set f [open $allccache r] + set allcwait 1 + getcache $f + }]} return } - set cmd [concat | git rev-list --all --parents] - foreach id $seeds { - lappend cmd "^$id" + if {$allcwait} { + return + } + set cmd [list | git rev-list --parents] + set allcupdate [expr {$seeds ne {}}] + if {!$allcupdate} { + set ids "--all" + } else { + set refs [concat [array names idheads] [array names idtags] \ + [array names idotherrefs]] + set ids {} + set tagobjs {} + foreach name [array names tagobjid] { + lappend tagobjs $tagobjid($name) + } + foreach id [lsort -unique $refs] { + if {![info exists allparents($id)] && + [lsearch -exact $tagobjs $id] < 0} { + lappend ids $id + } + } + if {$ids ne {}} { + foreach id $seeds { + lappend ids "^$id" + } + } + } + if {$ids ne {}} { + set fd [open [concat $cmd $ids] r] + fconfigure $fd -blocking 0 + incr allcommits + nowbusy allcommits + filerun $fd [list getallclines $fd] + } else { + dispneartags 0 } - set fd [open $cmd r] - fconfigure $fd -blocking 0 - incr allcommits - nowbusy allcommits - filerun $fd [list getallclines $fd] } # Since most commits have 1 parent and 1 child, we group strings of @@ -6475,10 +6723,10 @@ proc getallcommits {} { # coming from descendents, and "outgoing" means going towards ancestors. proc getallclines {fd} { - global allids allparents allchildren idtags idheads nextarc nbmp + global allparents allchildren idtags idheads nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing - global seeds allcommits - + global seeds allcommits cachedarcs allcupdate + set nid 0 while {[incr nid] <= 1000 && [gets $fd line] >= 0} { set id [lindex $line 0] @@ -6486,7 +6734,7 @@ proc getallclines {fd} { # seen it already continue } - lappend allids $id + set cachedarcs 0 set olds [lrange $line 1 end] set allparents($id) $olds if {![info exists allchildren($id)]} { @@ -6517,7 +6765,6 @@ proc getallclines {fd} { continue } } - incr nbmp foreach a $arcnos($id) { lappend arcids($a) $id set arcend($a) $id @@ -6557,9 +6804,28 @@ proc getallclines {fd} { if {![eof $fd]} { return [expr {$nid >= 1000? 2: 1}] } - close $fd + set cacheok 1 + if {[catch { + fconfigure $fd -blocking 1 + close $fd + } err]} { + # got an error reading the list of commits + # if we were updating, try rereading the whole thing again + if {$allcupdate} { + incr allcommits -1 + dropcache $err + return + } + error_popup "Error reading commit topology information;\ + branch and preceding/following tag information\ + will be incomplete.\n($err)" + set cacheok 0 + } if {[incr allcommits -1] == 0} { notbusy allcommits + if {$cacheok} { + run savecache + } } dispneartags 0 return 0 @@ -6583,7 +6849,7 @@ proc recalcarc {a} { } proc splitarc {p} { - global arcnos arcids nextarc nbmp arctags archeads idtags idheads + global arcnos arcids nextarc arctags archeads idtags idheads global arcstart arcend arcout allparents growing set a $arcnos($p) @@ -6615,7 +6881,6 @@ proc splitarc {p} { set growing($na) 1 unset growing($a) } - incr nbmp foreach id $tail { if {[llength $arcnos($id)] == 1} { @@ -6639,17 +6904,15 @@ proc splitarc {p} { # Update things for a new commit added that is a child of one # existing commit. Used when cherry-picking. proc addnewchild {id p} { - global allids allparents allchildren idtags nextarc nbmp + global allparents allchildren idtags nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing global seeds allcommits - if {![info exists allcommits]} return - lappend allids $id + if {![info exists allcommits] || ![info exists arcnos($p)]} return set allparents($id) [list $p] set allchildren($id) {} set arcnos($id) {} lappend seeds $id - incr nbmp lappend allchildren($p) $id set a [incr nextarc] set arcstart($a) $id @@ -6664,6 +6927,172 @@ proc addnewchild {id p} { set arcout($id) [list $a] } +# This implements a cache for the topology information. +# The cache saves, for each arc, the start and end of the arc, +# the ids on the arc, and the outgoing arcs from the end. +proc readcache {f} { + global arcnos arcids arcout arcstart arcend arctags archeads nextarc + global idtags idheads allparents cachedarcs possible_seeds seeds growing + global allcwait + + set a $nextarc + set lim $cachedarcs + if {$lim - $a > 500} { + set lim [expr {$a + 500}] + } + if {[catch { + if {$a == $lim} { + # finish reading the cache and setting up arctags, etc. + set line [gets $f] + if {$line ne "1"} {error "bad final version"} + close $f + foreach id [array names idtags] { + if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 && + [llength $allparents($id)] == 1} { + set a [lindex $arcnos($id) 0] + if {$arctags($a) eq {}} { + recalcarc $a + } + } + } + foreach id [array names idheads] { + if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 && + [llength $allparents($id)] == 1} { + set a [lindex $arcnos($id) 0] + if {$archeads($a) eq {}} { + recalcarc $a + } + } + } + foreach id [lsort -unique $possible_seeds] { + if {$arcnos($id) eq {}} { + lappend seeds $id + } + } + set allcwait 0 + } else { + while {[incr a] <= $lim} { + set line [gets $f] + if {[llength $line] != 3} {error "bad line"} + set s [lindex $line 0] + set arcstart($a) $s + lappend arcout($s) $a + if {![info exists arcnos($s)]} { + lappend possible_seeds $s + set arcnos($s) {} + } + set e [lindex $line 1] + if {$e eq {}} { + set growing($a) 1 + } else { + set arcend($a) $e + if {![info exists arcout($e)]} { + set arcout($e) {} + } + } + set arcids($a) [lindex $line 2] + foreach id $arcids($a) { + lappend allparents($s) $id + set s $id + lappend arcnos($id) $a + } + if {![info exists allparents($s)]} { + set allparents($s) {} + } + set arctags($a) {} + set archeads($a) {} + } + set nextarc [expr {$a - 1}] + } + } err]} { + dropcache $err + return 0 + } + if {!$allcwait} { + getallcommits + } + return $allcwait +} + +proc getcache {f} { + global nextarc cachedarcs possible_seeds + + if {[catch { + set line [gets $f] + if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"} + # make sure it's an integer + set cachedarcs [expr {int([lindex $line 1])}] + if {$cachedarcs < 0} {error "bad number of arcs"} + set nextarc 0 + set possible_seeds {} + run readcache $f + } err]} { + dropcache $err + } + return 0 +} + +proc dropcache {err} { + global allcwait nextarc cachedarcs seeds + + #puts "dropping cache ($err)" + foreach v {arcnos arcout arcids arcstart arcend growing \ + arctags archeads allparents allchildren} { + global $v + catch {unset $v} + } + set allcwait 0 + set nextarc 0 + set cachedarcs 0 + set seeds {} + getallcommits +} + +proc writecache {f} { + global cachearc cachedarcs allccache + global arcstart arcend arcnos arcids arcout + + set a $cachearc + set lim $cachedarcs + if {$lim - $a > 1000} { + set lim [expr {$a + 1000}] + } + if {[catch { + while {[incr a] <= $lim} { + if {[info exists arcend($a)]} { + puts $f [list $arcstart($a) $arcend($a) $arcids($a)] + } else { + puts $f [list $arcstart($a) {} $arcids($a)] + } + } + } err]} { + catch {close $f} + catch {file delete $allccache} + #puts "writing cache failed ($err)" + return 0 + } + set cachearc [expr {$a - 1}] + if {$a > $cachedarcs} { + puts $f "1" + close $f + return 0 + } + return 1 +} + +proc savecache {} { + global nextarc cachedarcs cachearc allccache + + if {$nextarc == $cachedarcs} return + set cachearc 0 + set cachedarcs $nextarc + catch { + set f [open $allccache w] + puts $f [list 1 $cachedarcs] + run writecache $f + } +} + # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a, # or 0 if neither is true. proc anc_or_desc {a b} { @@ -7361,6 +7790,7 @@ proc showtag {tag isnew} { } $ctext conf -state normal clear_ctext + settabs 0 set linknum 0 if {![info exists tagcontents($tag)]} { catch { @@ -7384,11 +7814,135 @@ proc doquit {} { destroy . } +proc mkfontdisp {font top which} { + global fontattr fontpref $font + + set fontpref($font) [set $font] + button $top.${font}but -text $which -font optionfont \ + -command [list choosefont $font $which] + label $top.$font -relief flat -font $font \ + -text $fontattr($font,family) -justify left + grid x $top.${font}but $top.$font -sticky w +} + +proc choosefont {font which} { + global fontparam fontlist fonttop fontattr + + set fontparam(which) $which + set fontparam(font) $font + set fontparam(family) [font actual $font -family] + set fontparam(size) $fontattr($font,size) + set fontparam(weight) $fontattr($font,weight) + set fontparam(slant) $fontattr($font,slant) + set top .gitkfont + set fonttop $top + if {![winfo exists $top]} { + font create sample + eval font config sample [font actual $font] + toplevel $top + wm title $top "Gitk font chooser" + label $top.l -textvariable fontparam(which) -font uifont + pack $top.l -side top + set fontlist [lsort [font families]] + frame $top.f + listbox $top.f.fam -listvariable fontlist \ + -yscrollcommand [list $top.f.sb set] + bind $top.f.fam <<ListboxSelect>> selfontfam + scrollbar $top.f.sb -command [list $top.f.fam yview] + pack $top.f.sb -side right -fill y + pack $top.f.fam -side left -fill both -expand 1 + pack $top.f -side top -fill both -expand 1 + frame $top.g + spinbox $top.g.size -from 4 -to 40 -width 4 \ + -textvariable fontparam(size) \ + -validatecommand {string is integer -strict %s} + checkbutton $top.g.bold -padx 5 \ + -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \ + -variable fontparam(weight) -onvalue bold -offvalue normal + checkbutton $top.g.ital -padx 5 \ + -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0 \ + -variable fontparam(slant) -onvalue italic -offvalue roman + pack $top.g.size $top.g.bold $top.g.ital -side left + pack $top.g -side top + canvas $top.c -width 150 -height 50 -border 2 -relief sunk \ + -background white + $top.c create text 100 25 -anchor center -text $which -font sample \ + -fill black -tags text + bind $top.c <Configure> [list centertext $top.c] + pack $top.c -side top -fill x + frame $top.buts + button $top.buts.ok -text "OK" -command fontok -default active \ + -font uifont + button $top.buts.can -text "Cancel" -command fontcan -default normal \ + -font uifont + grid $top.buts.ok $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + pack $top.buts -side bottom -fill x + trace add variable fontparam write chg_fontparam + } else { + raise $top + $top.c itemconf text -text $which + } + set i [lsearch -exact $fontlist $fontparam(family)] + if {$i >= 0} { + $top.f.fam selection set $i + $top.f.fam see $i + } +} + +proc centertext {w} { + $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}] +} + +proc fontok {} { + global fontparam fontpref prefstop + + set f $fontparam(font) + set fontpref($f) [list $fontparam(family) $fontparam(size)] + if {$fontparam(weight) eq "bold"} { + lappend fontpref($f) "bold" + } + if {$fontparam(slant) eq "italic"} { + lappend fontpref($f) "italic" + } + set w $prefstop.$f + $w conf -text $fontparam(family) -font $fontpref($f) + + fontcan +} + +proc fontcan {} { + global fonttop fontparam + + if {[info exists fonttop]} { + catch {destroy $fonttop} + catch {font delete sample} + unset fonttop + unset fontparam + } +} + +proc selfontfam {} { + global fonttop fontparam + + set i [$fonttop.f.fam curselection] + if {$i ne {}} { + set fontparam(family) [$fonttop.f.fam get $i] + } +} + +proc chg_fontparam {v sub op} { + global fontparam + + font config sample -$sub $fontparam($sub) +} + proc doprefs {} { - global maxwidth maxgraphpct diffopts + global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor - global uifont tabstop + global uifont tabstop limitdiffs set top .gitkprefs set prefstop $top @@ -7396,13 +7950,14 @@ proc doprefs {} { raise $top return } - foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} { + foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ + limitdiffs tabstop} { set oldprefs($v) [set $v] } toplevel $top wm title $top "Gitk preferences" label $top.ldisp -text "Commit list display options" - $top.ldisp configure -font $uifont + $top.ldisp configure -font uifont grid $top.ldisp - -sticky w -pady 10 label $top.spacer -text " " label $top.maxwidthl -text "Maximum graph width (lines)" \ @@ -7420,23 +7975,24 @@ proc doprefs {} { grid x $top.showlocal -sticky w label $top.ddisp -text "Diff display options" - $top.ddisp configure -font $uifont + $top.ddisp configure -font uifont grid $top.ddisp - -sticky w -pady 10 - label $top.diffoptl -text "Options for diff program" \ - -font optionfont - entry $top.diffopt -width 20 -textvariable diffopts - grid x $top.diffoptl $top.diffopt -sticky w + label $top.tabstopl -text "Tab spacing" -font optionfont + spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop + grid x $top.tabstopl $top.tabstop -sticky w frame $top.ntag label $top.ntag.l -text "Display nearby tags" -font optionfont checkbutton $top.ntag.b -variable showneartags pack $top.ntag.b $top.ntag.l -side left grid x $top.ntag -sticky w - label $top.tabstopl -text "tabstop" -font optionfont - spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop - grid x $top.tabstopl $top.tabstop -sticky w + frame $top.ldiff + label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont + checkbutton $top.ldiff.b -variable limitdiffs + pack $top.ldiff.b $top.ldiff.l -side left + grid x $top.ldiff -sticky w label $top.cdisp -text "Colors: press to choose" - $top.cdisp configure -font $uifont + $top.cdisp configure -font uifont grid $top.cdisp - -sticky w -pady 10 label $top.bg -padx 40 -relief sunk -background $bgcolor button $top.bgbut -text "Background" -font optionfont \ @@ -7467,11 +8023,18 @@ proc doprefs {} { -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg] grid x $top.selbgbut $top.selbgsep -sticky w + label $top.cfont -text "Fonts: press to choose" + $top.cfont configure -font uifont + grid $top.cfont - -sticky w -pady 10 + mkfontdisp mainfont $top "Main font" + mkfontdisp textfont $top "Diff display font" + mkfontdisp uifont $top "User interface font" + frame $top.buts button $top.buts.ok -text "OK" -command prefsok -default active - $top.buts.ok configure -font $uifont + $top.buts.ok configure -font uifont button $top.buts.can -text "Cancel" -command prefscan -default normal - $top.buts.can configure -font $uifont + $top.buts.can configure -font uifont grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -7519,24 +8082,48 @@ proc setfg {c} { } proc prefscan {} { - global maxwidth maxgraphpct diffopts - global oldprefs prefstop showneartags showlocalchanges + global oldprefs prefstop - foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} { + foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ + limitdiffs tabstop} { + global $v set $v $oldprefs($v) } catch {destroy $prefstop} unset prefstop + fontcan } proc prefsok {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges - global charspc ctext tabstop + global fontpref mainfont textfont uifont + global limitdiffs treediffs catch {destroy $prefstop} unset prefstop - $ctext configure -tabs "[expr {$tabstop * $charspc}]" + fontcan + set fontchanged 0 + if {$mainfont ne $fontpref(mainfont)} { + set mainfont $fontpref(mainfont) + parsefont mainfont $mainfont + eval font configure mainfont [fontflags mainfont] + eval font configure mainfontbold [fontflags mainfont 1] + setcoords + set fontchanged 1 + } + if {$textfont ne $fontpref(textfont)} { + set textfont $fontpref(textfont) + parsefont textfont $textfont + eval font configure textfont [fontflags textfont] + eval font configure textfontbold [fontflags textfont 1] + } + if {$uifont ne $fontpref(uifont)} { + set uifont $fontpref(uifont) + parsefont uifont $uifont + eval font configure uifont [fontflags uifont] + } + settabs if {$showlocalchanges != $oldprefs(showlocalchanges)} { if {$showlocalchanges} { doshowlocalchanges @@ -7544,10 +8131,15 @@ proc prefsok {} { dohidelocalchanges } } - if {$maxwidth != $oldprefs(maxwidth) + if {$limitdiffs != $oldprefs(limitdiffs)} { + # treediffs elements are limited by path + catch {unset treediffs} + } + if {$fontchanged || $maxwidth != $oldprefs(maxwidth) || $maxgraphpct != $oldprefs(maxgraphpct)} { redisplay - } elseif {$showneartags != $oldprefs(showneartags)} { + } elseif {$showneartags != $oldprefs(showneartags) || + $limitdiffs != $oldprefs(limitdiffs)} { reselectline } } @@ -7833,9 +8425,15 @@ proc tcl_encoding {enc} { return {} } +# First check that Tcl/Tk is recent enough +if {[catch {package require Tk 8.4} err]} { + show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ + Gitk requires at least Tcl/Tk 8.4." + exit 1 +} + # defaults... set datemode 0 -set diffopts "-U 5 -p" set wrcomcmd "git diff-tree --stdin -p --pretty" set gitencoding {} @@ -7859,15 +8457,16 @@ set maxgraphpct 50 set maxwidth 16 set revlistorder 0 set fastdate 0 -set uparrowlen 7 -set downarrowlen 7 -set mingaplen 30 +set uparrowlen 5 +set downarrowlen 5 +set mingaplen 100 set cmitmode "patch" set wrapcomment "none" set showneartags 1 set maxrefs 20 set maxlinelen 200 set showlocalchanges 1 +set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" set colors {green red blue magenta darkgrey brown orange} @@ -7881,6 +8480,17 @@ catch {source ~/.gitk} font create optionfont -family sans-serif -size -12 +parsefont mainfont $mainfont +eval font create mainfont [fontflags mainfont] +eval font create mainfontbold [fontflags mainfont 1] + +parsefont textfont $textfont +eval font create textfont [fontflags textfont] +eval font create textfontbold [fontflags textfont 1] + +parsefont uifont $uifont +eval font create uifont [fontflags uifont] + # check that we can find a .git directory somewhere... if {[catch {set gitdir [gitdir]}]} { show_error {} . "Cannot find a git repository here." @@ -7891,6 +8501,7 @@ if {![file isdirectory $gitdir]} { exit 1 } +set mergeonly 0 set revtreeargs {} set cmdline_files {} set i 0 @@ -7898,6 +8509,10 @@ foreach arg $argv { switch -- $arg { "" { } "-d" { set datemode 1 } + "--merge" { + set mergeonly 1 + lappend revtreeargs $arg + } "--" { set cmdline_files [lrange $argv [expr {$i + 1}] end] break @@ -7938,9 +8553,44 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} { } } +if {$mergeonly} { + # find the list of unmerged files + set mlist {} + set nr_unmerged 0 + if {[catch { + set fd [open "| git ls-files -u" r] + } err]} { + show_error {} . "Couldn't get list of unmerged files: $err" + exit 1 + } + while {[gets $fd line] >= 0} { + set i [string first "\t" $line] + if {$i < 0} continue + set fname [string range $line [expr {$i+1}] end] + if {[lsearch -exact $mlist $fname] >= 0} continue + incr nr_unmerged + if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} { + lappend mlist $fname + } + } + catch {close $fd} + if {$mlist eq {}} { + if {$nr_unmerged == 0} { + show_error {} . "No files selected: --merge specified but\ + no files are unmerged." + } else { + show_error {} . "No files selected: --merge specified but\ + no unmerged files are within file limit." + } + exit 1 + } + set cmdline_files $mlist +} + set nullid "0000000000000000000000000000000000000000" set nullid2 "0000000000000000000000000000000000000001" +set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] set runq {} set history {} @@ -7948,18 +8598,23 @@ set historyindex 0 set fh_serial 0 set nhl_names {} set highlight_paths {} +set findpattern {} set searchdirn -forwards set boldrows {} set boldnamerows {} set diffelide {0 0} set markingmatches 0 - -set optim_delay 16 +set linkentercount 0 +set need_redisplay 0 +set nrows_drawn 0 +set firsttabstop 0 set nextviewnum 1 set curview 0 set selectedview 0 set selectedhlview None +set highlight_related None +set highlight_files {} set viewfiles(0) {} set viewperm(0) 0 set viewargs(0) {} @@ -7968,7 +8623,6 @@ set cmdlineok 0 set stopped 0 set stuffsaved 0 set patchnum 0 -set lookingforhead 0 set localirow -1 set localfrow -1 set lserial 0 diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 1b8887987f..446a1c333b 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -85,6 +85,10 @@ div.title, a.title { color: #000000; } +div.readme { + padding: 8px; +} + a.title:hover { background-color: #d9d8d1; } @@ -170,14 +174,10 @@ a.text:hover { table { padding: 8px 4px; -} - -table.project_list { border-spacing: 0; } table.diff_tree { - border-spacing: 0; font-family: monospace; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9deefce0a6..491a3f41d2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -35,6 +35,10 @@ our $GIT = "++GIT_BINDIR++/git"; #our $projectroot = "/pub/scm"; our $projectroot = "++GITWEB_PROJECTROOT++"; +# fs traversing limit for getting project list +# the number is relative to the projectroot +our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; + # target of the home link on top of all pages our $home_link = $my_uri || "/"; @@ -607,6 +611,15 @@ sub href(%) { ); my %mapping = @mapping; + if ($params{-replay}) { + while (my ($name, $symbol) = each %mapping) { + if (!exists $params{$name}) { + # to allow for multivalued params we use arrayref form + $params{$name} = [ $cgi->param($symbol) ]; + } + } + } + $params{'project'} = $project unless exists $params{'project'}; my ($use_pathinfo) = gitweb_check_feature('pathinfo'); @@ -842,6 +855,23 @@ sub chop_str { return "$body$tail"; } +# takes the same arguments as chop_str, but also wraps a <span> around the +# result with a title attribute if it does get chopped. Additionally, the +# string is HTML-escaped. +sub chop_and_escape_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + my $chopped = chop_str($str, $len, $add_len); + if ($chopped eq $str) { + return esc_html($chopped); + } else { + return qq{<span title="} . esc_html($str) . qq{">} . + esc_html($chopped) . qq{</span>}; + } +} + ## ---------------------------------------------------------------------- ## functions returning short strings @@ -1402,20 +1432,121 @@ sub git_get_type { return $type; } +# repository configuration +our $config_file = ''; +our %config; + +# store multiple values for single key as anonymous array reference +# single values stored directly in the hash, not as [ <value> ] +sub hash_set_multi { + my ($hash, $key, $value) = @_; + + if (!exists $hash->{$key}) { + $hash->{$key} = $value; + } elsif (!ref $hash->{$key}) { + $hash->{$key} = [ $hash->{$key}, $value ]; + } else { + push @{$hash->{$key}}, $value; + } +} + +# return hash of git project configuration +# optionally limited to some section, e.g. 'gitweb' +sub git_parse_project_config { + my $section_regexp = shift; + my %config; + + local $/ = "\0"; + + open my $fh, "-|", git_cmd(), "config", '-z', '-l', + or return; + + while (my $keyval = <$fh>) { + chomp $keyval; + my ($key, $value) = split(/\n/, $keyval, 2); + + hash_set_multi(\%config, $key, $value) + if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o); + } + close $fh; + + return %config; +} + +# convert config value to boolean, 'true' or 'false' +# no value, number > 0, 'true' and 'yes' values are true +# rest of values are treated as false (never as error) +sub config_to_bool { + my $val = shift; + + # strip leading and trailing whitespace + $val =~ s/^\s+//; + $val =~ s/\s+$//; + + return (!defined $val || # section.key + ($val =~ /^\d+$/ && $val) || # section.key = 1 + ($val =~ /^(?:true|yes)$/i)); # section.key = true +} + +# convert config value to simple decimal number +# an optional value suffix of 'k', 'm', or 'g' will cause the value +# to be multiplied by 1024, 1048576, or 1073741824 +sub config_to_int { + my $val = shift; + + # strip leading and trailing whitespace + $val =~ s/^\s+//; + $val =~ s/\s+$//; + + if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) { + $unit = lc($unit); + # unknown unit is treated as 1 + return $num * ($unit eq 'g' ? 1073741824 : + $unit eq 'm' ? 1048576 : + $unit eq 'k' ? 1024 : 1); + } + return $val; +} + +# convert config value to array reference, if needed +sub config_to_multi { + my $val = shift; + + return ref($val) ? $val : [ $val ]; +} + sub git_get_project_config { my ($key, $type) = @_; + # key sanity check return unless ($key); $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); - my @x = (git_cmd(), 'config'); - if (defined $type) { push @x, $type; } - push @x, "--get"; - push @x, "gitweb.$key"; - my $val = qx(@x); - chomp $val; - return ($val); + # type sanity check + if (defined $type) { + $type =~ s/^--//; + $type = undef + unless ($type eq 'bool' || $type eq 'int'); + } + + # get config + if (!defined $config_file || + $config_file ne "$git_dir/config") { + %config = git_parse_project_config('gitweb'); + $config_file = "$git_dir/config"; + } + + # ensure given type + if (!defined $type) { + return $config{"gitweb.$key"}; + } elsif ($type eq 'bool') { + # backward compatibility: 'git config --bool' returns true/false + return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false'; + } elsif ($type eq 'int') { + return config_to_int($config{"gitweb.$key"}); + } + return $config{"gitweb.$key"}; } # get hash of given path at given ref @@ -1475,7 +1606,9 @@ sub git_get_path_by_hash { sub git_get_project_description { my $path = shift; - open my $fd, "$projectroot/$path/description" or return undef; + $git_dir = "$projectroot/$path"; + open my $fd, "$projectroot/$path/description" + or return git_get_project_config('description'); my $descr = <$fd>; close $fd; if (defined $descr) { @@ -1487,7 +1620,11 @@ sub git_get_project_description { sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/cloneurl" or return; + $git_dir = "$projectroot/$path"; + open my $fd, "$projectroot/$path/cloneurl" + or return wantarray ? + @{ config_to_multi(git_get_project_config('url')) } : + config_to_multi(git_get_project_config('url')); my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; @@ -1509,6 +1646,7 @@ sub git_get_projects_list { # remove the trailing "/" $dir =~ s!/+$!!; my $pfxlen = length("$dir"); + my $pfxdepth = ($dir =~ tr!/!!); File::Find::find({ follow_fast => 1, # follow symbolic links @@ -1519,6 +1657,11 @@ sub git_get_projects_list { return if (m!^[/.]$!); # only directories can be git repositories return unless (-d $_); + # don't traverse too deep (Find is super slow on os x) + if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { + $File::Find::prune = 1; + return; + } my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot @@ -1963,12 +2106,12 @@ sub parse_difftree_raw_line { $res{'to_mode'} = $2; $res{'from_id'} = $3; $res{'to_id'} = $4; - $res{'status'} = $5; + $res{'status'} = $res{'status_str'} = $5; $res{'similarity'} = $6; if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); } else { - $res{'file'} = unquote($7); + $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7); } } # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' @@ -1979,6 +2122,7 @@ sub parse_difftree_raw_line { $res{'to_mode'} = pop @{$res{'from_mode'}}; $res{'from_id'} = [ split(' ', $3) ]; $res{'to_id'} = pop @{$res{'from_id'}}; + $res{'status_str'} = $4; $res{'status'} = [ split('', $4) ]; $res{'to_file'} = unquote($5); } @@ -1990,6 +2134,19 @@ sub parse_difftree_raw_line { return wantarray ? %res : \%res; } +# wrapper: return parsed line of git-diff-tree "raw" output +# (the argument might be raw line, or parsed info) +sub parsed_difftree_line { + my $line_or_ref = shift; + + if (ref($line_or_ref) eq "HASH") { + # pre-parsed (or generated by hand) + return $line_or_ref; + } else { + return parse_difftree_raw_line($line_or_ref); + } +} + # parse line of git-ls-tree output sub parse_ls_tree_line ($;%) { my $line = shift; @@ -2022,7 +2179,10 @@ sub parse_from_to_diffinfo { fill_from_file_info($diffinfo, @parents) unless exists $diffinfo->{'from_file'}; for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { - $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'}; + $from->{'file'}[$i] = + defined $diffinfo->{'from_file'}[$i] ? + $diffinfo->{'from_file'}[$i] : + $diffinfo->{'to_file'}; if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file $from->{'href'}[$i] = href(action=>"blob", hash_base=>$parents[$i], @@ -2033,7 +2193,8 @@ sub parse_from_to_diffinfo { } } } else { - $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; + # ordinary (not combined) diff + $from->{'file'} = $diffinfo->{'from_file'}; if ($diffinfo->{'status'} ne "A") { # not new (added) file $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent, hash=>$diffinfo->{'from_id'}, @@ -2043,7 +2204,7 @@ sub parse_from_to_diffinfo { } } - $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; + $to->{'file'} = $diffinfo->{'to_file'}; if (!is_deleted($diffinfo)) { # file exists in result $to->{'href'} = href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, @@ -2464,7 +2625,7 @@ sub format_paging_nav { if ($page > 0) { $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1), + $cgi->a({-href => href(-replay=>1, page=>$page-1), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= " ⋅ prev"; @@ -2472,7 +2633,7 @@ sub format_paging_nav { if ($nrevs >= (100 * ($page+1)-1)) { $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1), + $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } else { $paging_nav .= " ⋅ next"; @@ -2756,6 +2917,7 @@ sub git_print_tree_entry { ## ...................................................................... ## functions printing large fragments of HTML +# get pre-image filenames for merge (combined) diff sub fill_from_file_info { my ($diff, @parents) = @_; @@ -2772,28 +2934,25 @@ sub fill_from_file_info { return $diff; } -# parameters can be strings, or references to arrays of strings -sub from_ids_eq { - my ($a, $b) = @_; +# is current raw difftree line of file deletion +sub is_deleted { + my $diffinfo = shift; - if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) { - for (my $i = 0; $i < @$a; ++$i) { - return 0 unless ($a->[$i] eq $b->[$i]); - } - return 1; - } elsif (!ref($a) && !ref($b)) { - return $a eq $b; - } else { - return 0; - } + return $diffinfo->{'status_str'} =~ /D/; } -sub is_deleted { - my $diffinfo = shift; +# does patch correspond to [previous] difftree raw line +# $diffinfo - hashref of parsed raw diff format +# $patchinfo - hashref of parsed patch diff format +# (the same keys as in $diffinfo) +sub is_patch_split { + my ($diffinfo, $patchinfo) = @_; - return $diffinfo->{'to_id'} eq ('0' x 40); + return defined $diffinfo && defined $patchinfo + && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'}; } + sub git_difftree_body { my ($difftree, $hash, @parents) = @_; my ($parent) = $parents[0]; @@ -2830,13 +2989,7 @@ sub git_difftree_body { my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { - my $diff; - if (ref($line) eq "HASH") { - # pre-parsed (or generated by hand) - $diff = $line; - } else { - $diff = parse_difftree_raw_line($line); - } + my $diff = parsed_difftree_line($line); if ($alternate) { print "<tr class=\"dark\">\n"; @@ -3107,10 +3260,12 @@ sub git_patchset_body { my ($fd, $difftree, $hash, @hash_parents) = @_; my ($hash_parent) = $hash_parents[0]; + my $is_combined = (@hash_parents > 1); my $patch_idx = 0; my $patch_number = 0; my $patch_line; my $diffinfo; + my $to_name; my (%from, %to); print "<div class=\"patchset\">\n"; @@ -3124,73 +3279,46 @@ sub git_patchset_body { PATCH: while ($patch_line) { - my @diff_header; - my ($from_id, $to_id); - - # git diff header - #assert($patch_line =~ m/^diff /) if DEBUG; - #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed - $patch_number++; - push @diff_header, $patch_line; - - # extended diff header - EXTENDED_HEADER: - while ($patch_line = <$fd>) { - chomp $patch_line; - - last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); - - if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = $1; - $to_id = $2; - } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = [ split(',', $1) ]; - $to_id = $2; - } - push @diff_header, $patch_line; + # parse "git diff" header line + if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) { + # $1 is from_name, which we do not use + $to_name = unquote($2); + $to_name =~ s!^b/!!; + } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) { + # $1 is 'cc' or 'combined', which we do not use + $to_name = unquote($2); + } else { + $to_name = undef; } - my $last_patch_line = $patch_line; # check if current patch belong to current raw line # and parse raw git-diff line if needed - if (defined $diffinfo && - defined $from_id && defined $to_id && - from_ids_eq($diffinfo->{'from_id'}, $from_id) && - $diffinfo->{'to_id'} eq $to_id) { + if (is_patch_split($diffinfo, { 'to_file' => $to_name })) { # this is continuation of a split patch print "<div class=\"patch cont\">\n"; } else { # advance raw git-diff output if needed $patch_idx++ if defined $diffinfo; - # compact combined diff output can have some patches skipped - # find which patch (using pathname of result) we are at now - my $to_name; - if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) { - $to_name = $1; - } - - do { - # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + # read and prepare patch information + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); - # check if current raw line has no patch (it got simplified) - if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) { + # compact combined diff output can have some patches skipped + # find which patch (using pathname of result) we are at now; + if ($is_combined) { + while ($to_name ne $diffinfo->{'to_file'}) { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . format_diff_cc_simplified($diffinfo, @hash_parents) . "</div>\n"; # class="patch" $patch_idx++; $patch_number++; + + last if $patch_idx > $#$difftree; + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); } - } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || - $patch_idx > $#$difftree); + } # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); @@ -3200,30 +3328,36 @@ sub git_patchset_body { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n"; } + # git diff header + #assert($patch_line =~ m/^diff /) if DEBUG; + #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed + $patch_number++; # print "git diff" header - $patch_line = shift @diff_header; print format_git_diff_header_line($patch_line, $diffinfo, \%from, \%to); # print extended diff header - print "<div class=\"diff extended_header\">\n" if (@diff_header > 0); + print "<div class=\"diff extended_header\">\n"; EXTENDED_HEADER: - foreach $patch_line (@diff_header) { + while ($patch_line = <$fd>) { + chomp $patch_line; + + last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); + print format_extended_diff_header_line($patch_line, $diffinfo, \%from, \%to); } - print "</div>\n" if (@diff_header > 0); # class="diff extended_header" + print "</div>\n"; # class="diff extended_header" # from-file/to-file diff header - $patch_line = $last_patch_line; if (! $patch_line) { print "</div>\n"; # class="patch" last PATCH; } next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; - #assert($patch_line eq $last_patch_line) if DEBUG; + my $last_patch_line = $patch_line; $patch_line = <$fd>; chomp $patch_line; #assert($patch_line =~ m/^\+\+\+/) if DEBUG; @@ -3248,16 +3382,11 @@ sub git_patchset_body { # for compact combined (--cc) format, with chunk and patch simpliciaction # patchset might be empty, but there might be unprocessed raw lines - for ($patch_idx++ if $patch_number > 0; + for (++$patch_idx if $patch_number > 0; $patch_idx < @$difftree; - $patch_idx++) { + ++$patch_idx) { # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); # generate anchor for "patch" links in difftree / whatchanged part print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . @@ -3385,7 +3514,7 @@ sub git_project_list_body { "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list", -title => $pr->{'descr_long'}}, esc_html($pr->{'descr'})) . "</td>\n" . - "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n"; + "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n"; print "<td class=\"". age_class($pr->{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" . "<td class=\"link\">" . @@ -3415,7 +3544,7 @@ sub git_shortlog_body { $from = 0 unless defined $from; $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); - print "<table class=\"shortlog\" cellspacing=\"0\">\n"; + print "<table class=\"shortlog\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my %co = %{$commitlist->[$i]}; @@ -3427,9 +3556,10 @@ sub git_shortlog_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 10); # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); @@ -3460,7 +3590,7 @@ sub git_history_body { $from = 0 unless defined $from; $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist}); - print "<table class=\"history\" cellspacing=\"0\">\n"; + print "<table class=\"history\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my %co = %{$commitlist->[$i]}; @@ -3477,9 +3607,10 @@ sub git_history_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + # shortlog uses chop_str($co{'author_name'}, 10) + my $author = chop_and_escape_str($co{'author_name'}, 15, 3); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - # shortlog uses chop_str($co{'author_name'}, 10) - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; # originally git_history used chop_str($co{'title'}, 50) print format_subject_html($co{'title'}, $co{'title_short'}, @@ -3519,7 +3650,7 @@ sub git_tags_body { $from = 0 unless defined $from; $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); - print "<table class=\"tags\" cellspacing=\"0\">\n"; + print "<table class=\"tags\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; @@ -3582,7 +3713,7 @@ sub git_heads_body { $from = 0 unless defined $from; $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); - print "<table class=\"heads\" cellspacing=\"0\">\n"; + print "<table class=\"heads\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; @@ -3619,7 +3750,7 @@ sub git_search_grep_body { $from = 0 unless defined $from; $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); - print "<table class=\"grep\" cellspacing=\"0\">\n"; + print "<table class=\"commit_search\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my %co = %{$commitlist->[$i]}; @@ -3633,11 +3764,12 @@ sub git_search_grep_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($search_regexp)(.*)$/i) { @@ -3759,7 +3891,7 @@ sub git_summary { git_print_page_nav('summary','', $head); print "<div class=\"title\"> </div>\n"; - print "<table cellspacing=\"0\">\n" . + print "<table class=\"projects_list\">\n" . "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . "<tr><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n"; if (defined $cd{'rfc2822'}) { @@ -3780,8 +3912,10 @@ sub git_summary { if (-s "$projectroot/$project/README.html") { if (open my $fd, "$projectroot/$project/README.html") { - print "<div class=\"title\">readme</div>\n"; + print "<div class=\"title\">readme</div>\n" . + "<div class=\"readme\">\n"; print $_ while (<$fd>); + print "\n</div>\n"; # class="readme" close $fd; } } @@ -3833,7 +3967,7 @@ sub git_tag { git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . - "<table cellspacing=\"0\">\n" . + "<table class=\"object_header\">\n" . "<tr>\n" . "<td>object</td>\n" . "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, @@ -3886,11 +4020,11 @@ sub git_blame2 { or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = - $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"blob", -replay=>1)}, "blob") . " | " . - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, - "history") . + $cgi->a({-href => href(action=>"history", -replay=>1)}, + "history") . " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "HEAD"); @@ -4166,18 +4300,15 @@ sub git_blob { if (defined $file_name) { if ($have_blame) { $formats_nav .= - $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base, - hash=>$hash, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"blame", -replay=>1)}, "blame") . " | "; } $formats_nav .= - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, - hash=>$hash, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . - $cgi->a({-href => href(action=>"blob_plain", - hash=>$hash, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"blob_plain", -replay=>1)}, "raw") . " | " . $cgi->a({-href => href(action=>"blob", @@ -4185,7 +4316,8 @@ sub git_blob { "HEAD"); } else { $formats_nav .= - $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); + $cgi->a({-href => href(action=>"blob_plain", -replay=>1)}, + "raw"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -4248,8 +4380,7 @@ sub git_tree { my @views_nav = (); if (defined $file_name) { push @views_nav, - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, - hash=>$hash, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"history", -replay=>1)}, "history"), $cgi->a({-href => href(action=>"tree", hash_base=>"HEAD", file_name=>$file_name)}, @@ -4276,7 +4407,7 @@ sub git_tree { } git_print_page_path($file_name, 'tree', $hash_base); print "<div class=\"page_body\">\n"; - print "<table cellspacing=\"0\">\n"; + print "<table class=\"tree\">\n"; my $alternate = 1; # '..' (top directory) link if possible if (defined $hash_base && @@ -4423,7 +4554,7 @@ sub git_log { } if ($#commitlist >= 100) { print "<div class=\"page_nav\">\n"; - print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1), + print $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); print "</div>\n"; } @@ -4498,7 +4629,7 @@ sub git_commit { git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "<div class=\"title_text\">\n" . - "<table cellspacing=\"0\">\n"; + "<table class=\"object_header\">\n"; print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n". "<tr>" . "<td></td><td> $ad{'rfc2822'}"; @@ -4655,8 +4786,8 @@ sub git_blobdiff { } %diffinfo = parse_difftree_raw_line($difftree[0]); - $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'}; - $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'}; + $file_parent ||= $diffinfo{'from_file'} || $file_name; + $file_name ||= $diffinfo{'to_file'}; $hash_parent ||= $diffinfo{'from_id'}; $hash ||= $diffinfo{'to_id'}; @@ -4717,10 +4848,7 @@ sub git_blobdiff { # header if ($format eq 'html') { my $formats_nav = - $cgi->a({-href => href(action=>"blobdiff_plain", - hash=>$hash, hash_parent=>$hash_parent, - hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, - file_name=>$file_name, file_parent=>$file_parent)}, + $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)}, "raw"); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { @@ -4794,8 +4922,7 @@ sub git_commitdiff { my $formats_nav; if ($format eq 'html') { $formats_nav = - $cgi->a({-href => href(action=>"commitdiff_plain", - hash=>$hash, hash_parent=>$hash_parent)}, + $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); if (defined $hash_parent && @@ -4990,27 +5117,20 @@ sub git_history { file_name=>$file_name)}, "first"); $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, - file_name=>$file_name, page=>$page-1), + $cgi->a({-href => href(-replay=>1, page=>$page-1), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= "first"; $paging_nav .= " ⋅ prev"; } - if ($#commitlist >= 100) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, - file_name=>$file_name, page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - $paging_nav .= " ⋅ next"; - } my $next_link = ''; if ($#commitlist >= 100) { $next_link = - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, - file_name=>$file_name, page=>$page+1), + $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); + $paging_nav .= " ⋅ $next_link"; + } else { + $paging_nav .= " ⋅ next"; } git_header_html(); @@ -5080,30 +5200,23 @@ sub git_search { searchtext=>$searchtext, searchtype=>$searchtype)}, "first"); $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page-1), + $cgi->a({-href => href(-replay=>1, page=>$page-1), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= "first"; $paging_nav .= " ⋅ prev"; } + my $next_link = ''; if ($#commitlist >= 100) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page+1), + $next_link = + $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); + $paging_nav .= " ⋅ $next_link"; } else { $paging_nav .= " ⋅ next"; } - my $next_link = ''; + if ($#commitlist >= 100) { - $next_link = - $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); } git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); @@ -5115,7 +5228,7 @@ sub git_search { git_print_page_nav('','', $hash,$co{'tree'},$hash); git_print_header_div('commit', esc_html($co{'title'}), $hash); - print "<table cellspacing=\"0\">\n"; + print "<table class=\"pickaxe search\">\n"; my $alternate = 1; $/ = "\n"; my $git_command = git_cmd_str(); @@ -5147,12 +5260,13 @@ sub git_search { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); while (my $setref = shift @files) { my %set = %$setref; print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, @@ -5181,7 +5295,7 @@ sub git_search { git_print_page_nav('','', $hash,$co{'tree'},$hash); git_print_header_div('commit', esc_html($co{'title'}), $hash); - print "<table cellspacing=\"0\">\n"; + print "<table class=\"grep_search\">\n"; my $alternate = 1; my $matches = 0; $/ = "\n"; @@ -5301,7 +5415,7 @@ sub git_shortlog { my $next_link = ''; if ($#commitlist >= 100) { $next_link = - $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), + $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } diff --git a/hash.c b/hash.c new file mode 100644 index 0000000000..7b492d4fc0 --- /dev/null +++ b/hash.c @@ -0,0 +1,110 @@ +/* + * Some generic hashing helpers. + */ +#include "cache.h" +#include "hash.h" + +/* + * Look up a hash entry in the hash table. Return the pointer to + * the existing entry, or the empty slot if none existed. The caller + * can then look at the (*ptr) to see whether it existed or not. + */ +static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table) +{ + unsigned int size = table->size, nr = hash % size; + struct hash_table_entry *array = table->array; + + while (array[nr].ptr) { + if (array[nr].hash == hash) + break; + nr++; + if (nr >= size) + nr = 0; + } + return array + nr; +} + + +/* + * Insert a new hash entry pointer into the table. + * + * If that hash entry already existed, return the pointer to + * the existing entry (and the caller can create a list of the + * pointers or do anything else). If it didn't exist, return + * NULL (and the caller knows the pointer has been inserted). + */ +static void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table) +{ + struct hash_table_entry *entry = lookup_hash_entry(hash, table); + + if (!entry->ptr) { + entry->ptr = ptr; + entry->hash = hash; + table->nr++; + return NULL; + } + return &entry->ptr; +} + +static void grow_hash_table(struct hash_table *table) +{ + unsigned int i; + unsigned int old_size = table->size, new_size; + struct hash_table_entry *old_array = table->array, *new_array; + + new_size = alloc_nr(old_size); + new_array = xcalloc(sizeof(struct hash_table_entry), new_size); + table->size = new_size; + table->array = new_array; + table->nr = 0; + for (i = 0; i < old_size; i++) { + unsigned int hash = old_array[i].hash; + void *ptr = old_array[i].ptr; + if (ptr) + insert_hash_entry(hash, ptr, table); + } + free(old_array); +} + +void *lookup_hash(unsigned int hash, struct hash_table *table) +{ + if (!table->array) + return NULL; + return &lookup_hash_entry(hash, table)->ptr; +} + +void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) +{ + unsigned int nr = table->nr; + if (nr >= table->size/2) + grow_hash_table(table); + return insert_hash_entry(hash, ptr, table); +} + +int for_each_hash(struct hash_table *table, int (*fn)(void *)) +{ + int sum = 0; + unsigned int i; + unsigned int size = table->size; + struct hash_table_entry *array = table->array; + + for (i = 0; i < size; i++) { + void *ptr = array->ptr; + array++; + if (ptr) { + int val = fn(ptr); + if (val < 0) + return val; + sum += val; + } + } + return sum; +} + +void free_hash(struct hash_table *table) +{ + free(table->array); + table->array = NULL; + table->size = 0; + table->nr = 0; +} diff --git a/hash.h b/hash.h new file mode 100644 index 0000000000..a8b0fbb5b5 --- /dev/null +++ b/hash.h @@ -0,0 +1,43 @@ +#ifndef HASH_H +#define HASH_H + +/* + * These are some simple generic hash table helper functions. + * Not necessarily suitable for all users, but good for things + * where you want to just keep track of a list of things, and + * have a good hash to use on them. + * + * It keeps the hash table at roughly 50-75% free, so the memory + * cost of the hash table itself is roughly + * + * 3 * 2*sizeof(void *) * nr_of_objects + * + * bytes. + * + * FIXME: on 64-bit architectures, we waste memory. It would be + * good to have just 32-bit pointers, requiring a special allocator + * for hashed entries or something. + */ +struct hash_table_entry { + unsigned int hash; + void *ptr; +}; + +struct hash_table { + unsigned int size, nr; + struct hash_table_entry *array; +}; + +extern void *lookup_hash(unsigned int hash, struct hash_table *table); +extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table); +extern int for_each_hash(struct hash_table *table, int (*fn)(void *)); +extern void free_hash(struct hash_table *table); + +static inline void init_hash(struct hash_table *table) +{ + table->size = 0; + table->nr = 0; + table->array = NULL; +} + +#endif @@ -7,7 +7,6 @@ #include "builtin.h" #include "exec_cmd.h" #include "common-cmds.h" -#include <sys/ioctl.h> /* most GUI terminals set COLUMNS (although some don't export it) */ static int term_columns(void) @@ -37,24 +36,25 @@ static inline void mput_char(char c, unsigned int num) putchar(c); } -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; +static struct cmdnames { + int alloc; + int cnt; + struct cmdname { + size_t len; + char name[1]; + } **names; +} main_cmds, other_cmds; -static void add_cmdname(const char *name, int len) +static void add_cmdname(struct cmdnames *cmds, const char *name, int len) { - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - } - ent = xmalloc(sizeof(*ent) + len); + struct cmdname *ent = xmalloc(sizeof(*ent) + len); + ent->len = len; memcpy(ent->name, name, len); ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; + + ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc); + cmds->names[cmds->cnt++] = ent; } static int cmdname_compare(const void *a_, const void *b_) @@ -64,7 +64,43 @@ static int cmdname_compare(const void *a_, const void *b_) return strcmp(a->name, b->name); } -static void pretty_print_string_list(struct cmdname **cmdname, int longest) +static void uniq(struct cmdnames *cmds) +{ + int i, j; + + if (!cmds->cnt) + return; + + for (i = j = 1; i < cmds->cnt; i++) + if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name)) + cmds->names[j++] = cmds->names[i]; + + cmds->cnt = j; +} + +static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) +{ + int ci, cj, ei; + int cmp; + + ci = cj = ei = 0; + while (ci < cmds->cnt && ei < excludes->cnt) { + cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name); + if (cmp < 0) + cmds->names[cj++] = cmds->names[ci++]; + else if (cmp == 0) + ci++, ei++; + else if (cmp > 0) + ei++; + } + + while (ci < cmds->cnt) + cmds->names[cj++] = cmds->names[ci++]; + + cmds->cnt = cj; +} + +static void pretty_print_string_list(struct cmdnames *cmds, int longest) { int cols = 1, rows; int space = longest + 1; /* min 1 SP between words */ @@ -73,9 +109,7 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) if (space < max_cols) cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + rows = (cmds->cnt + cols - 1) / cols; for (i = 0; i < rows; i++) { printf(" "); @@ -83,71 +117,112 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) for (j = 0; j < cols; j++) { int n = j * rows + i; int size = space; - if (n >= cmdname_cnt) + if (n >= cmds->cnt) break; - if (j == cols-1 || n + rows >= cmdname_cnt) + if (j == cols-1 || n + rows >= cmds->cnt) size = 1; - printf("%-*s", size, cmdname[n]->name); + printf("%-*s", size, cmds->names[n]->name); } putchar('\n'); } } -static void list_commands(const char *exec_path, const char *pattern) +static unsigned int list_commands_in_dir(struct cmdnames *cmds, + const char *path) { unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); + const char *prefix = "git-"; + int prefix_len = strlen(prefix); + DIR *dir = opendir(path); struct dirent *de; - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); - } - - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; + if (!dir || chdir(path)) + return 0; while ((de = readdir(dir)) != NULL) { struct stat st; int entlen; - if (prefixcmp(de->d_name, "git-")) + if (prefixcmp(de->d_name, prefix)) continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ + + if (stat(de->d_name, &st) || /* stat, not lstat */ !S_ISREG(st.st_mode) || !(st.st_mode & S_IXUSR)) continue; - entlen = strlen(de->d_name); + entlen = strlen(de->d_name) - prefix_len; if (has_extension(de->d_name, ".exe")) entlen -= 4; if (longest < entlen) longest = entlen; - add_cmdname(de->d_name + 4, entlen-4); + add_cmdname(cmds, de->d_name + prefix_len, entlen); } closedir(dir); - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); + return longest; } -static void list_common_cmds_help(void) +static void list_commands(void) +{ + unsigned int longest = 0; + unsigned int len; + const char *env_path = getenv("PATH"); + char *paths, *path, *colon; + const char *exec_path = git_exec_path(); + + if (exec_path) + longest = list_commands_in_dir(&main_cmds, exec_path); + + if (!env_path) { + fprintf(stderr, "PATH not set\n"); + exit(1); + } + + path = paths = xstrdup(env_path); + while (1) { + if ((colon = strchr(path, ':'))) + *colon = 0; + + len = list_commands_in_dir(&other_cmds, path); + if (len > longest) + longest = len; + + if (!colon) + break; + path = colon + 1; + } + free(paths); + + qsort(main_cmds.names, main_cmds.cnt, + sizeof(*main_cmds.names), cmdname_compare); + uniq(&main_cmds); + + qsort(other_cmds.names, other_cmds.cnt, + sizeof(*other_cmds.names), cmdname_compare); + uniq(&other_cmds); + exclude_cmds(&other_cmds, &main_cmds); + + if (main_cmds.cnt) { + printf("available git commands in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(&main_cmds, longest); + putchar('\n'); + } + + if (other_cmds.cnt) { + printf("git commands available from elsewhere on your $PATH\n"); + printf("---------------------------------------------------\n"); + pretty_print_string_list(&other_cmds, longest); + putchar('\n'); + } +} + +void list_common_cmds_help(void) { int i, longest = 0; @@ -185,8 +260,7 @@ static void show_man_page(const char *git_cmd) void help_unknown_cmd(const char *cmd) { - printf("git: '%s' is not a git-command\n\n", cmd); - list_common_cmds_help(); + fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); exit(1); } @@ -199,19 +273,17 @@ int cmd_version(int argc, const char **argv, const char *prefix) int cmd_help(int argc, const char **argv, const char *prefix) { const char *help_cmd = argc > 1 ? argv[1] : NULL; - const char *exec_path = git_exec_path(); if (!help_cmd) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); - exit(1); + exit(0); } else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { printf("usage: %s\n\n", git_usage_string); - if(exec_path) - list_commands(exec_path, "git-*"); - exit(1); + list_commands(); + exit(0); } else diff --git a/http-push.c b/http-push.c index 7c3720f602..78283b4de3 100644 --- a/http-push.c +++ b/http-push.c @@ -1,7 +1,6 @@ #include "cache.h" #include "commit.h" #include "pack.h" -#include "fetch.h" #include "tag.h" #include "blob.h" #include "http.h" @@ -14,7 +13,7 @@ #include <expat.h> static const char http_push_usage[] = -"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n"; +"git-http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n"; #ifndef XML_STATUS_OK enum XML_Status { @@ -79,8 +78,9 @@ static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; static int push_verbosely; -static int push_all; +static int push_all = MATCH_REFS_NONE; static int force_all; +static int dry_run; static struct object_list *objects; @@ -433,7 +433,7 @@ static void start_fetch_packed(struct transfer_request *request) packfile = fopen(request->tmpfile, "a"); if (!packfile) { fprintf(stderr, "Unable to open local file %s for pack", - filename); + request->tmpfile); remote->can_update_info_refs = 0; free(url); return; @@ -795,38 +795,27 @@ static void finish_request(struct transfer_request *request) } #ifdef USE_CURL_MULTI -void fill_active_slots(void) +static int fill_active_slot(void *unused) { struct transfer_request *request = request_queue_head; - struct transfer_request *next; - struct active_request_slot *slot = active_queue_head; - int num_transfers; if (aborted) - return; + return 0; - while (active_requests < max_requests && request != NULL) { - next = request->next; + for (request = request_queue_head; request; request = request->next) { if (request->state == NEED_FETCH) { start_fetch_loose(request); + return 1; } else if (pushing && request->state == NEED_PUSH) { if (remote_dir_exists[request->obj->sha1[0]] == 1) { start_put(request); } else { start_mkcol(request); } - curl_multi_perform(curlm, &num_transfers); - } - request = next; - } - - while (slot != NULL) { - if (!slot->in_use && slot->curl != NULL) { - curl_easy_cleanup(slot->curl); - slot->curl = NULL; + return 1; } - slot = slot->next; } + return 0; } #endif @@ -952,7 +941,7 @@ static int fetch_index(unsigned char *sha1) indexfile = fopen(tmpfile, "a"); if (!indexfile) return error("Unable to open local file %s for pack index", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; @@ -1271,10 +1260,7 @@ xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; free(ctx->cdata); - ctx->cdata = xmalloc(len + 1); - /* NB: 's' is not null-terminated, can not use strlcpy here */ - memcpy(ctx->cdata, s, len); - ctx->cdata[len] = '\0'; + ctx->cdata = xmemdupz(s, len); } static struct remote_lock *lock_remote(const char *path, long timeout) @@ -2172,9 +2158,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) /* If it's a symref, set the refname; otherwise try for a sha1 */ if (!prefixcmp((char *)buffer.buffer, "ref: ")) { - *symref = xmalloc(buffer.posn - 5); - memcpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6); - (*symref)[buffer.posn - 6] = '\0'; + *symref = xmemdupz((char *)buffer.buffer + 5, buffer.posn - 6); } else { get_sha1_hex(buffer.buffer, sha1); } @@ -2257,7 +2241,11 @@ static int delete_remote_branch(char *pattern, int force) /* Remote branch must be an ancestor of remote HEAD */ if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) { - return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern); + return error("The branch '%s' is not an ancestor " + "of your current HEAD.\n" + "If you are sure you want to delete it," + " run:\n\t'git http-push -D %s %s'", + remote_ref->name, remote->url, pattern); } } @@ -2312,13 +2300,17 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--all")) { - push_all = 1; + push_all = MATCH_REFS_ALL; continue; } if (!strcmp(arg, "--force")) { force_all = 1; continue; } + if (!strcmp(arg, "--dry-run")) { + dry_run = 1; + continue; + } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; continue; @@ -2401,7 +2393,7 @@ int main(int argc, char **argv) if (!remote_tail) remote_tail = &remote_refs; if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, push_all)) + nr_refspec, (const char **) refspec, push_all)) return -1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); @@ -2429,16 +2421,17 @@ int main(int argc, char **argv) if (!has_sha1_file(ref->old_sha1) || !ref_newer(ref->peer_ref->new_sha1, ref->old_sha1)) { - /* We do not have the remote ref, or + /* + * We do not have the remote ref, or * we know that the remote ref is not * an ancestor of what we are trying to * push. Either way this can be losing * commits at the remote end and likely * we were not up to date to begin with. */ - error("remote '%s' is not a strict " - "subset of local ref '%s'. " - "maybe you are not up-to-date and " + error("remote '%s' is not an ancestor of\n" + "local '%s'.\n" + "Maybe you are not up-to-date and " "need to pull first?", ref->name, ref->peer_ref->name); @@ -2460,7 +2453,8 @@ int main(int argc, char **argv) if (strcmp(ref->name, ref->peer_ref->name)) fprintf(stderr, " using '%s'", ref->peer_ref->name); fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); - + if (dry_run) + continue; /* Lock remote branch ref */ ref_lock = lock_remote(ref->name, LOCK_TIME); @@ -2507,6 +2501,7 @@ int main(int argc, char **argv) objects_to_send); #ifdef USE_CURL_MULTI fill_active_slots(); + add_fill_function(NULL, fill_active_slot); #endif finish_all_active_slots(); @@ -2527,7 +2522,8 @@ int main(int argc, char **argv) if (remote->has_info_refs && new_refs) { if (info_ref_lock && remote->can_update_info_refs) { fprintf(stderr, "Updating remote server info\n"); - update_remote_info_refs(info_ref_lock); + if (!dry_run) + update_remote_info_refs(info_ref_lock); } else { fprintf(stderr, "Unable to update server info\n"); } diff --git a/http-fetch.c b/http-walker.c index 202fae0ba8..a3fb596542 100644 --- a/http-fetch.c +++ b/http-walker.c @@ -1,19 +1,12 @@ #include "cache.h" #include "commit.h" #include "pack.h" -#include "fetch.h" +#include "walker.h" #include "http.h" #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 -static int commits_on_stdin; - -static int got_alternates = -1; -static int corrupt_object_found; - -static struct curl_slist *no_pragma_header; - struct alt_base { char *base; @@ -22,8 +15,6 @@ struct alt_base struct alt_base *next; }; -static struct alt_base *alt; - enum object_request_state { WAITING, ABORTED, @@ -33,6 +24,7 @@ enum object_request_state { struct object_request { + struct walker *walker; unsigned char sha1[20]; struct alt_base *repo; char *url; @@ -53,6 +45,7 @@ struct object_request }; struct alternates_request { + struct walker *walker; const char *base; char *url; struct buffer *buffer; @@ -60,6 +53,13 @@ struct alternates_request { int http_specific; }; +struct walker_data { + const char *url; + int got_alternates; + struct alt_base *alt; + struct curl_slist *no_pragma_header; +}; + static struct object_request *object_queue_head; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, @@ -103,11 +103,12 @@ static int missing__target(int code, int result) #define missing_target(a) missing__target((a)->http_code, (a)->curl_result) -static void fetch_alternates(const char *base); +static void fetch_alternates(struct walker *walker, const char *base); static void process_object_response(void *callback_data); -static void start_object_request(struct object_request *obj_req) +static void start_object_request(struct walker *walker, + struct object_request *obj_req) { char *hex = sha1_to_hex(obj_req->sha1); char prevfile[PATH_MAX]; @@ -120,6 +121,7 @@ static void start_object_request(struct object_request *obj_req) char range[RANGE_HEADER_SIZE]; struct curl_slist *range_header = NULL; struct active_request_slot *slot; + struct walker_data *data = walker->data; snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename); unlink(prevfile); @@ -212,12 +214,12 @@ static void start_object_request(struct object_request *obj_req) curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr); curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); /* If we have successfully processed data from a previous fetch attempt, only fetch the data we don't already have. */ if (prev_posn>0) { - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Resuming fetch of object %s at byte %ld\n", hex, prev_posn); @@ -268,13 +270,16 @@ static void finish_object_request(struct object_request *obj_req) move_temp_to_file(obj_req->tmpfile, obj_req->filename); if (obj_req->rename == 0) - pull_say("got %s\n", sha1_to_hex(obj_req->sha1)); + walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1)); } static void process_object_response(void *callback_data) { struct object_request *obj_req = (struct object_request *)callback_data; + struct walker *walker = obj_req->walker; + struct walker_data *data = walker->data; + struct alt_base *alt = data->alt; obj_req->curl_result = obj_req->slot->curl_result; obj_req->http_code = obj_req->slot->http_code; @@ -283,13 +288,13 @@ static void process_object_response(void *callback_data) /* Use alternates if necessary */ if (missing_target(obj_req)) { - fetch_alternates(alt->base); + fetch_alternates(walker, alt->base); if (obj_req->repo->next != NULL) { obj_req->repo = obj_req->repo->next; close(obj_req->local); obj_req->local = -1; - start_object_request(obj_req); + start_object_request(walker, obj_req); return; } } @@ -317,42 +322,35 @@ static void release_object_request(struct object_request *obj_req) } #ifdef USE_CURL_MULTI -void fill_active_slots(void) +static int fill_active_slot(struct walker *walker) { - struct object_request *obj_req = object_queue_head; - struct active_request_slot *slot = active_queue_head; - int num_transfers; + struct object_request *obj_req; - while (active_requests < max_requests && obj_req != NULL) { + for (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) { if (obj_req->state == WAITING) { if (has_sha1_file(obj_req->sha1)) obj_req->state = COMPLETE; - else - start_object_request(obj_req); - curl_multi_perform(curlm, &num_transfers); - } - obj_req = obj_req->next; - } - - while (slot != NULL) { - if (!slot->in_use && slot->curl != NULL) { - curl_easy_cleanup(slot->curl); - slot->curl = NULL; + else { + start_object_request(walker, obj_req); + return 1; + } } - slot = slot->next; } + return 0; } #endif -void prefetch(unsigned char *sha1) +static void prefetch(struct walker *walker, unsigned char *sha1) { struct object_request *newreq; struct object_request *tail; + struct walker_data *data = walker->data; char *filename = sha1_file_name(sha1); newreq = xmalloc(sizeof(*newreq)); + newreq->walker = walker; hashcpy(newreq->sha1, sha1); - newreq->repo = alt; + newreq->repo = data->alt; newreq->url = NULL; newreq->local = -1; newreq->state = WAITING; @@ -378,7 +376,7 @@ void prefetch(unsigned char *sha1) #endif } -static int fetch_index(struct alt_base *repo, unsigned char *sha1) +static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1) { char *hex = sha1_to_hex(sha1); char *filename; @@ -387,6 +385,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) long prev_posn = 0; char range[RANGE_HEADER_SIZE]; struct curl_slist *range_header = NULL; + struct walker_data *data = walker->data; FILE *indexfile; struct active_request_slot *slot; @@ -395,7 +394,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) if (has_pack_index(sha1)) return 0; - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Getting index for pack %s\n", hex); url = xmalloc(strlen(repo->base) + 64); @@ -406,21 +405,21 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) indexfile = fopen(tmpfile, "a"); if (!indexfile) return error("Unable to open local file %s for pack index", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); slot->local = indexfile; /* If there is data present from a previous transfer attempt, resume where it left off */ prev_posn = ftell(indexfile); if (prev_posn>0) { - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Resuming fetch of index for pack %s at byte %ld\n", hex, prev_posn); @@ -446,13 +445,13 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) return move_temp_to_file(tmpfile, filename); } -static int setup_index(struct alt_base *repo, unsigned char *sha1) +static int setup_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1) { struct packed_git *new_pack; if (has_pack_file(sha1)) return 0; /* don't list this as something we can get */ - if (fetch_index(repo, sha1)) + if (fetch_index(walker, repo, sha1)) return -1; new_pack = parse_pack_index(sha1); @@ -465,8 +464,10 @@ static void process_alternates_response(void *callback_data) { struct alternates_request *alt_req = (struct alternates_request *)callback_data; + struct walker *walker = alt_req->walker; + struct walker_data *cdata = walker->data; struct active_request_slot *slot = alt_req->slot; - struct alt_base *tail = alt; + struct alt_base *tail = cdata->alt; const char *base = alt_req->base; static const char null_byte = '\0'; char *data; @@ -487,7 +488,7 @@ static void process_alternates_response(void *callback_data) if (slot->finished != NULL) (*slot->finished) = 0; if (!start_active_slot(slot)) { - got_alternates = -1; + cdata->got_alternates = -1; slot->in_use = 0; if (slot->finished != NULL) (*slot->finished) = 1; @@ -496,7 +497,7 @@ static void process_alternates_response(void *callback_data) } } else if (slot->curl_result != CURLE_OK) { if (!missing_target(slot)) { - got_alternates = -1; + cdata->got_alternates = -1; return; } } @@ -573,7 +574,7 @@ static void process_alternates_response(void *callback_data) memcpy(target + serverlen, data + i, posn - i - 7); target[serverlen + posn - i - 7] = 0; - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Also look at %s\n", target); newalt = xmalloc(sizeof(*newalt)); @@ -590,39 +591,40 @@ static void process_alternates_response(void *callback_data) i = posn + 1; } - got_alternates = 1; + cdata->got_alternates = 1; } -static void fetch_alternates(const char *base) +static void fetch_alternates(struct walker *walker, const char *base) { struct buffer buffer; char *url; char *data; struct active_request_slot *slot; struct alternates_request alt_req; + struct walker_data *cdata = walker->data; /* If another request has already started fetching alternates, wait for them to arrive and return to processing this request's curl message */ #ifdef USE_CURL_MULTI - while (got_alternates == 0) { + while (cdata->got_alternates == 0) { step_active_slots(); } #endif /* Nothing to do if they've already been fetched */ - if (got_alternates == 1) + if (cdata->got_alternates == 1) return; /* Start the fetch */ - got_alternates = 0; + cdata->got_alternates = 0; data = xmalloc(4096); buffer.size = 4096; buffer.posn = 0; buffer.buffer = data; - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Getting alternates list for %s\n", base); url = xmalloc(strlen(base) + 31); @@ -632,6 +634,7 @@ static void fetch_alternates(const char *base) may fail and need to have alternates loaded before continuing */ slot = get_active_slot(); slot->callback_func = process_alternates_response; + alt_req.walker = walker; slot->callback_data = &alt_req; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); @@ -647,13 +650,13 @@ static void fetch_alternates(const char *base) if (start_active_slot(slot)) run_active_slot(slot); else - got_alternates = -1; + cdata->got_alternates = -1; free(data); free(url); } -static int fetch_indices(struct alt_base *repo) +static int fetch_indices(struct walker *walker, struct alt_base *repo) { unsigned char sha1[20]; char *url; @@ -672,7 +675,7 @@ static int fetch_indices(struct alt_base *repo) buffer.posn = 0; buffer.buffer = data; - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Getting pack list for %s\n", repo->base); url = xmalloc(strlen(repo->base) + 21); @@ -712,7 +715,7 @@ static int fetch_indices(struct alt_base *repo) !prefixcmp(data + i, " pack-") && !prefixcmp(data + i + 46, ".pack\n")) { get_sha1_hex(data + i + 6, sha1); - setup_index(repo, sha1); + setup_index(walker, repo, sha1); i += 51; break; } @@ -728,7 +731,7 @@ static int fetch_indices(struct alt_base *repo) return 0; } -static int fetch_pack(struct alt_base *repo, unsigned char *sha1) +static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1) { char *url; struct packed_git *target; @@ -740,17 +743,18 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) long prev_posn = 0; char range[RANGE_HEADER_SIZE]; struct curl_slist *range_header = NULL; + struct walker_data *data = walker->data; struct active_request_slot *slot; struct slot_results results; - if (fetch_indices(repo)) + if (fetch_indices(walker, repo)) return -1; target = find_sha1_pack(sha1, repo->packs); if (!target) return -1; - if (get_verbosely) { + if (walker->get_verbosely) { fprintf(stderr, "Getting pack %s\n", sha1_to_hex(target->sha1)); fprintf(stderr, " which contains %s\n", @@ -766,21 +770,21 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) packfile = fopen(tmpfile, "a"); if (!packfile) return error("Unable to open local file %s for pack", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); slot->local = packfile; /* If there is data present from a previous transfer attempt, resume where it left off */ prev_posn = ftell(packfile); if (prev_posn>0) { - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, "Resuming fetch of pack %s at byte %ld\n", sha1_to_hex(target->sha1), prev_posn); @@ -834,7 +838,7 @@ static void abort_object_request(struct object_request *obj_req) release_object_request(obj_req); } -static int fetch_object(struct alt_base *repo, unsigned char *sha1) +static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1) { char *hex = sha1_to_hex(sha1); int ret = 0; @@ -855,7 +859,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) step_active_slots(); } #else - start_object_request(obj_req); + start_object_request(walker, obj_req); #endif while (obj_req->state == ACTIVE) { @@ -876,7 +880,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) obj_req->errorstr, obj_req->curl_result, obj_req->http_code, hex); } else if (obj_req->zret != Z_STREAM_END) { - corrupt_object_found++; + walker->corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { ret = error("File %s has bad hash", hex); @@ -889,20 +893,21 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) return ret; } -int fetch(unsigned char *sha1) +static int fetch(struct walker *walker, unsigned char *sha1) { - struct alt_base *altbase = alt; + struct walker_data *data = walker->data; + struct alt_base *altbase = data->alt; - if (!fetch_object(altbase, sha1)) + if (!fetch_object(walker, altbase, sha1)) return 0; while (altbase) { - if (!fetch_pack(altbase, sha1)) + if (!fetch_pack(walker, altbase, sha1)) return 0; - fetch_alternates(alt->base); + fetch_alternates(walker, data->alt->base); altbase = altbase->next; } return error("Unable to find %s under %s", sha1_to_hex(sha1), - alt->base); + data->alt->base); } static inline int needs_quote(int ch) @@ -951,12 +956,13 @@ static char *quote_ref_url(const char *base, const char *ref) return qref; } -int fetch_ref(char *ref, unsigned char *sha1) +static int fetch_ref(struct walker *walker, char *ref, unsigned char *sha1) { char *url; char hex[42]; struct buffer buffer; - const char *base = alt->base; + struct walker_data *data = walker->data; + const char *base = data->alt->base; struct active_request_slot *slot; struct slot_results results; buffer.size = 41; @@ -985,80 +991,45 @@ int fetch_ref(char *ref, unsigned char *sha1) return 0; } -int main(int argc, const char **argv) +static void cleanup(struct walker *walker) +{ + struct walker_data *data = walker->data; + http_cleanup(); + + curl_slist_free_all(data->no_pragma_header); +} + +struct walker *get_http_walker(const char *url) { - int commits; - const char **write_ref = NULL; - char **commit_id; - const char *url; char *s; - int arg = 1; - int rc = 0; - - setup_git_directory(); - git_config(git_default_config); - - while (arg < argc && argv[arg][0] == '-') { - if (argv[arg][1] == 't') { - get_tree = 1; - } else if (argv[arg][1] == 'c') { - get_history = 1; - } else if (argv[arg][1] == 'a') { - get_all = 1; - get_tree = 1; - get_history = 1; - } else if (argv[arg][1] == 'v') { - get_verbosely = 1; - } else if (argv[arg][1] == 'w') { - write_ref = &argv[arg + 1]; - arg++; - } else if (!strcmp(argv[arg], "--recover")) { - get_recover = 1; - } else if (!strcmp(argv[arg], "--stdin")) { - commits_on_stdin = 1; - } - arg++; - } - if (argc < arg + 2 - commits_on_stdin) { - usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); - return 1; - } - if (commits_on_stdin) { - commits = pull_targets_stdin(&commit_id, &write_ref); - } else { - commit_id = (char **) &argv[arg++]; - commits = 1; - } - url = argv[arg]; + struct walker_data *data = xmalloc(sizeof(struct walker_data)); + struct walker *walker = xmalloc(sizeof(struct walker)); http_init(); - no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + data->no_pragma_header = curl_slist_append(NULL, "Pragma:"); - alt = xmalloc(sizeof(*alt)); - alt->base = xmalloc(strlen(url) + 1); - strcpy(alt->base, url); - for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s) + data->alt = xmalloc(sizeof(*data->alt)); + data->alt->base = xmalloc(strlen(url) + 1); + strcpy(data->alt->base, url); + for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s) *s = 0; - alt->got_indices = 0; - alt->packs = NULL; - alt->next = NULL; - if (pull(commits, commit_id, write_ref, url)) - rc = 1; - - http_cleanup(); + data->alt->got_indices = 0; + data->alt->packs = NULL; + data->alt->next = NULL; + data->got_alternates = -1; - curl_slist_free_all(no_pragma_header); + walker->corrupt_object_found = 0; + walker->fetch = fetch; + walker->fetch_ref = fetch_ref; + walker->prefetch = prefetch; + walker->cleanup = cleanup; + walker->data = data; - if (commits_on_stdin) - pull_targets_free(commits, commit_id, write_ref); +#ifdef USE_CURL_MULTI + add_fill_function(walker, (int (*)(void *)) fill_active_slot); +#endif - if (corrupt_object_found) { - fprintf(stderr, -"Some loose object were found to be corrupt, but they might be just\n" -"a false '404 Not Found' error message sent with incorrect HTTP\n" -"status code. Suggest running git-fsck.\n"); - } - return rc; + return walker; } @@ -276,6 +276,7 @@ void http_cleanup(void) #endif while (slot != NULL) { + struct active_request_slot *next = slot->next; #ifdef USE_CURL_MULTI if (slot->in_use) { curl_easy_getinfo(slot->curl, @@ -287,8 +288,10 @@ void http_cleanup(void) #endif if (slot->curl != NULL) curl_easy_cleanup(slot->curl); - slot = slot->next; + free(slot); + slot = next; } + active_queue_head = NULL; #ifndef NO_CURL_EASY_DUPHANDLE curl_easy_cleanup(curl_default); @@ -300,7 +303,7 @@ void http_cleanup(void) curl_global_cleanup(); curl_slist_free_all(pragma_header); - pragma_header = NULL; + pragma_header = NULL; } struct active_request_slot *get_active_slot(void) @@ -372,6 +375,7 @@ int start_active_slot(struct active_request_slot *slot) { #ifdef USE_CURL_MULTI CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); + int num_transfers; if (curlm_result != CURLM_OK && curlm_result != CURLM_CALL_MULTI_PERFORM) { @@ -379,11 +383,60 @@ int start_active_slot(struct active_request_slot *slot) slot->in_use = 0; return 0; } + + /* + * We know there must be something to do, since we just added + * something. + */ + curl_multi_perform(curlm, &num_transfers); #endif return 1; } #ifdef USE_CURL_MULTI +struct fill_chain { + void *data; + int (*fill)(void *); + struct fill_chain *next; +}; + +static struct fill_chain *fill_cfg = NULL; + +void add_fill_function(void *data, int (*fill)(void *)) +{ + struct fill_chain *new = malloc(sizeof(*new)); + struct fill_chain **linkp = &fill_cfg; + new->data = data; + new->fill = fill; + new->next = NULL; + while (*linkp) + linkp = &(*linkp)->next; + *linkp = new; +} + +void fill_active_slots(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (active_requests < max_requests) { + struct fill_chain *fill; + for (fill = fill_cfg; fill; fill = fill->next) + if (fill->fill(fill->data)) + break; + + if (!fill) + break; + } + + while (slot != NULL) { + if (!slot->in_use && slot->curl != NULL) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + } + slot = slot->next; + } +} + void step_active_slots(void) { int num_transfers; @@ -70,6 +70,7 @@ extern void release_active_slot(struct active_request_slot *slot); #ifdef USE_CURL_MULTI extern void fill_active_slots(void); +extern void add_fill_function(void *data, int (*fill)(void *)); extern void step_active_slots(void); #endif @@ -79,10 +80,6 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; -#ifdef USE_CURL_MULTI -extern int max_requests; -extern CURLM *curlm; -#endif #ifndef NO_CURL_EASY_DUPHANDLE extern CURL *curl_default; #endif @@ -103,6 +100,4 @@ extern long curl_low_speed_time; extern struct curl_slist *pragma_header; extern struct curl_slist *no_range_header; -extern struct active_request_slot *active_queue_head; - #endif /* HTTP_H */ diff --git a/imap-send.c b/imap-send.c index a5a0696084..a429a76a63 100644 --- a/imap-send.c +++ b/imap-send.c @@ -105,6 +105,19 @@ static void free_generic_messages( message_t * ); static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); +static int nfvasprintf(char **strp, const char *fmt, va_list ap) +{ + int len; + char tmp[8192]; + + len = vsnprintf(tmp, sizeof(tmp), fmt, ap); + if (len < 0) + die("Fatal: Out of memory\n"); + if (len >= sizeof(tmp)) + die("imap command overflow !\n"); + *strp = xmemdupz(tmp, len); + return len; +} static void arc4_init( void ); static unsigned char arc4_getbyte( void ); @@ -623,9 +636,7 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) goto bail; cur->len = s - p; s++; - cur->val = xmalloc( cur->len + 1 ); - memcpy( cur->val, p, cur->len ); - cur->val[cur->len] = 0; + cur->val = xmemdupz(p, cur->len); } else { /* atom */ p = s; @@ -633,12 +644,10 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) if (level && *s == ')') break; cur->len = s - p; - if (cur->len == 3 && !memcmp ("NIL", p, 3)) + if (cur->len == 3 && !memcmp ("NIL", p, 3)) { cur->val = NIL; - else { - cur->val = xmalloc( cur->len + 1 ); - memcpy( cur->val, p, cur->len ); - cur->val[cur->len] = 0; + } else { + cur->val = xmemdupz(p, cur->len); } } @@ -1160,28 +1169,18 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) static int read_message( FILE *f, msg_data_t *msg ) { - int len, r; + struct strbuf buf; - memset( msg, 0, sizeof *msg ); - len = CHUNKSIZE; - msg->data = xmalloc( len+1 ); - msg->data[0] = 0; - - while(!feof( f )) { - if (msg->len >= len) { - void *p; - len += CHUNKSIZE; - p = xrealloc(msg->data, len+1); - if (!p) - break; - msg->data = p; - } - r = fread( &msg->data[msg->len], 1, len - msg->len, f ); - if (r <= 0) + memset(msg, 0, sizeof(*msg)); + strbuf_init(&buf, 0); + + do { + if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0) break; - msg->len += r; - } - msg->data[msg->len] = 0; + } while (!feof(f)); + + msg->len = buf.len; + msg->data = strbuf_detach(&buf, NULL); return msg->len; } @@ -1231,13 +1230,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) if (p) msg->len = &p[1] - data; - msg->data = xmalloc( msg->len + 1 ); - if (!msg->data) - return 0; - - memcpy( msg->data, data, msg->len ); - msg->data[ msg->len ] = 0; - + msg->data = xmemdupz(data, msg->len); *ofs += msg->len; return 1; } diff --git a/index-pack.c b/index-pack.c index c232e3fc78..9fd6982a97 100644 --- a/index-pack.c +++ b/index-pack.c @@ -46,7 +46,7 @@ static int nr_resolved_deltas; static int from_stdin; static int verbose; -static struct progress progress; +static struct progress *progress; /* We always read in 4kB chunks. */ static unsigned char input_buffer[4096]; @@ -88,6 +88,8 @@ static void *fill(int min) die("read error on input: %s", strerror(errno)); } input_len += ret; + if (from_stdin) + display_throughput(progress, consumed_bytes + input_len); } while (input_len < min); return input_buffer; } @@ -106,7 +108,7 @@ static void use(int bytes) consumed_bytes += bytes; } -static const char *open_pack_file(const char *pack_name) +static char *open_pack_file(char *pack_name) { if (from_stdin) { input_fd = 0; @@ -406,7 +408,9 @@ static void parse_pack_objects(unsigned char *sha1) * - remember base (SHA1 or offset) for all deltas. */ if (verbose) - start_progress(&progress, "Indexing %u objects...", "", nr_objects); + progress = start_progress( + from_stdin ? "Receiving objects" : "Indexing objects", + nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; data = unpack_raw_entry(obj, &delta->base); @@ -418,12 +422,10 @@ static void parse_pack_objects(unsigned char *sha1) } else sha1_object(data, obj->size, obj->type, obj->idx.sha1); free(data); - if (verbose) - display_progress(&progress, i+1); + display_progress(progress, i+1); } objects[i].idx.offset = consumed_bytes; - if (verbose) - stop_progress(&progress); + stop_progress(&progress); /* Check pack integrity */ flush(); @@ -455,7 +457,7 @@ static void parse_pack_objects(unsigned char *sha1) * for some more deltas. */ if (verbose) - start_progress(&progress, "Resolving %u deltas...", "", nr_deltas); + progress = start_progress("Resolving deltas", nr_deltas); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; union delta_base base; @@ -486,8 +488,7 @@ static void parse_pack_objects(unsigned char *sha1) obj->size, obj->type); } free(data); - if (verbose) - display_progress(&progress, nr_resolved_deltas); + display_progress(progress, nr_resolved_deltas); } } @@ -594,8 +595,7 @@ static void fix_unresolved_deltas(int nr_unresolved) die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); append_obj_to_pack(d->base.sha1, data, size, type); free(data); - if (verbose) - display_progress(&progress, nr_resolved_deltas); + display_progress(progress, nr_resolved_deltas); } free(sorted_by_pos); } @@ -683,18 +683,31 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } } +static int git_index_pack_config(const char *k, const char *v) +{ + if (!strcmp(k, "pack.indexversion")) { + pack_idx_default_version = git_config_int(k, v); + if (pack_idx_default_version > 2) + die("bad pack.indexversion=%d", pack_idx_default_version); + return 0; + } + return git_default_config(k, v); +} + int main(int argc, char **argv) { int i, fix_thin_pack = 0; - const char *curr_pack, *pack_name = NULL; - const char *curr_index, *index_name = NULL; + char *curr_pack, *pack_name = NULL; + char *curr_index, *index_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; unsigned char sha1[20]; + git_config(git_index_pack_config); + for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + char *arg = argv[i]; if (*arg == '-') { if (!strcmp(arg, "--stdin")) { @@ -774,12 +787,12 @@ int main(int argc, char **argv) deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); parse_pack_objects(sha1); if (nr_deltas == nr_resolved_deltas) { - if (verbose) - stop_progress(&progress); + stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); } else { if (fix_thin_pack) { + char msg[48]; int nr_unresolved = nr_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) @@ -788,13 +801,11 @@ int main(int argc, char **argv) (nr_objects + nr_unresolved + 1) * sizeof(*objects)); fix_unresolved_deltas(nr_unresolved); - if (verbose) { - stop_progress(&progress); - fprintf(stderr, "%d objects were added to complete this thin pack.\n", - nr_objects - nr_objects_initial); - } + sprintf(msg, "completed with %d local objects", + nr_objects - nr_objects_initial); + stop_progress_msg(&progress, msg); fixup_pack_header_footer(output_fd, sha1, - curr_pack, nr_objects); + curr_pack, nr_objects); } if (nr_deltas != nr_resolved_deltas) die("pack has %d unresolved deltas", @@ -815,6 +826,10 @@ int main(int argc, char **argv) free(objects); free(index_name_buf); free(keep_name_buf); + if (pack_name == NULL) + free(curr_pack); + if (index_name == NULL) + free(curr_index); return 0; } diff --git a/interpolate.c b/interpolate.c index 00826778fc..6ef53f2465 100644 --- a/interpolate.c +++ b/interpolate.c @@ -44,9 +44,8 @@ void interp_clear_table(struct interp *table, int ninterps) * { "%%", "%"}, * } * - * Returns 0 on a successful substitution pass that fits in result, - * Returns a number of bytes needed to hold the full substituted - * string otherwise. + * Returns the length of the substituted string (not including the final \0). + * Like with snprintf, if the result is >= reslen, then it overflowed. */ unsigned long interpolate(char *result, unsigned long reslen, @@ -61,8 +60,6 @@ unsigned long interpolate(char *result, unsigned long reslen, int i; char c; - memset(result, 0, reslen); - while ((c = *src)) { if (c == '%') { /* Try to match an interpolation string. */ @@ -76,11 +73,15 @@ unsigned long interpolate(char *result, unsigned long reslen, /* Check for valid interpolation. */ if (i < ninterps) { value = interps[i].value; - valuelen = strlen(value); + if (!value) { + src += namelen; + continue; + } - if (newlen + valuelen + 1 < reslen) { + valuelen = strlen(value); + if (newlen + valuelen < reslen) { /* Substitute. */ - strncpy(dest, value, valuelen); + memcpy(dest, value, valuelen); dest += valuelen; } newlen += valuelen; @@ -95,8 +96,9 @@ unsigned long interpolate(char *result, unsigned long reslen, newlen++; } - if (newlen + 1 < reslen) - return 0; - else - return newlen + 2; + /* XXX: the previous loop always keep room for the ending NUL, + we just need to check if there was room for a NUL in the first place */ + if (reslen > 0) + *dest = '\0'; + return newlen; } diff --git a/local-fetch.c b/local-fetch.c deleted file mode 100644 index bf7ec6c2a3..0000000000 --- a/local-fetch.c +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2005 Junio C Hamano - */ -#include "cache.h" -#include "commit.h" -#include "fetch.h" - -static int use_link; -static int use_symlink; -static int use_filecopy = 1; -static int commits_on_stdin; - -static const char *path; /* "Remote" git repository */ - -void prefetch(unsigned char *sha1) -{ -} - -static struct packed_git *packs; - -static void setup_index(unsigned char *sha1) -{ - struct packed_git *new_pack; - char filename[PATH_MAX]; - strcpy(filename, path); - strcat(filename, "/objects/pack/pack-"); - strcat(filename, sha1_to_hex(sha1)); - strcat(filename, ".idx"); - new_pack = parse_pack_index_file(sha1, filename); - new_pack->next = packs; - packs = new_pack; -} - -static int setup_indices(void) -{ - DIR *dir; - struct dirent *de; - char filename[PATH_MAX]; - unsigned char sha1[20]; - sprintf(filename, "%s/objects/pack/", path); - dir = opendir(filename); - if (!dir) - return -1; - while ((de = readdir(dir)) != NULL) { - int namelen = strlen(de->d_name); - if (namelen != 50 || - !has_extension(de->d_name, ".pack")) - continue; - get_sha1_hex(de->d_name + 5, sha1); - setup_index(sha1); - } - closedir(dir); - return 0; -} - -static int copy_file(const char *source, char *dest, const char *hex, - int warn_if_not_exists) -{ - safe_create_leading_directories(dest); - if (use_link) { - if (!link(source, dest)) { - pull_say("link %s\n", hex); - return 0; - } - /* If we got ENOENT there is no point continuing. */ - if (errno == ENOENT) { - if (!warn_if_not_exists) - return -1; - return error("does not exist %s", source); - } - } - if (use_symlink) { - struct stat st; - if (stat(source, &st)) { - if (!warn_if_not_exists && errno == ENOENT) - return -1; - return error("cannot stat %s: %s", source, - strerror(errno)); - } - if (!symlink(source, dest)) { - pull_say("symlink %s\n", hex); - return 0; - } - } - if (use_filecopy) { - int ifd, ofd, status = 0; - - ifd = open(source, O_RDONLY); - if (ifd < 0) { - if (!warn_if_not_exists && errno == ENOENT) - return -1; - return error("cannot open %s", source); - } - ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (ofd < 0) { - close(ifd); - return error("cannot open %s", dest); - } - status = copy_fd(ifd, ofd); - close(ofd); - if (status) - return error("cannot write %s", dest); - pull_say("copy %s\n", hex); - return 0; - } - return error("failed to copy %s with given copy methods.", hex); -} - -static int fetch_pack(const unsigned char *sha1) -{ - struct packed_git *target; - char filename[PATH_MAX]; - if (setup_indices()) - return -1; - target = find_sha1_pack(sha1, packs); - if (!target) - return error("Couldn't find %s: not separate or in any pack", - sha1_to_hex(sha1)); - if (get_verbosely) { - fprintf(stderr, "Getting pack %s\n", - sha1_to_hex(target->sha1)); - fprintf(stderr, " which contains %s\n", - sha1_to_hex(sha1)); - } - sprintf(filename, "%s/objects/pack/pack-%s.pack", - path, sha1_to_hex(target->sha1)); - copy_file(filename, sha1_pack_name(target->sha1), - sha1_to_hex(target->sha1), 1); - sprintf(filename, "%s/objects/pack/pack-%s.idx", - path, sha1_to_hex(target->sha1)); - copy_file(filename, sha1_pack_index_name(target->sha1), - sha1_to_hex(target->sha1), 1); - install_packed_git(target); - return 0; -} - -static int fetch_file(const unsigned char *sha1) -{ - static int object_name_start = -1; - static char filename[PATH_MAX]; - char *hex = sha1_to_hex(sha1); - char *dest_filename = sha1_file_name(sha1); - - if (object_name_start < 0) { - strcpy(filename, path); /* e.g. git.git */ - strcat(filename, "/objects/"); - object_name_start = strlen(filename); - } - filename[object_name_start+0] = hex[0]; - filename[object_name_start+1] = hex[1]; - filename[object_name_start+2] = '/'; - strcpy(filename + object_name_start + 3, hex + 2); - return copy_file(filename, dest_filename, hex, 0); -} - -int fetch(unsigned char *sha1) -{ - if (has_sha1_file(sha1)) - return 0; - else - return fetch_file(sha1) && fetch_pack(sha1); -} - -int fetch_ref(char *ref, unsigned char *sha1) -{ - static int ref_name_start = -1; - static char filename[PATH_MAX]; - static char hex[41]; - int ifd; - - if (ref_name_start < 0) { - sprintf(filename, "%s/refs/", path); - ref_name_start = strlen(filename); - } - strcpy(filename + ref_name_start, ref); - ifd = open(filename, O_RDONLY); - if (ifd < 0) { - close(ifd); - return error("cannot open %s", filename); - } - if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) { - close(ifd); - return error("cannot read from %s", filename); - } - close(ifd); - pull_say("ref %s\n", sha1_to_hex(sha1)); - return 0; -} - -static const char local_pull_usage[] = -"git-local-fetch [-c] [-t] [-a] [-v] [-w filename] [--recover] [-l] [-s] [-n] [--stdin] commit-id path"; - -/* - * By default we only use file copy. - * If -l is specified, a hard link is attempted. - * If -s is specified, then a symlink is attempted. - * If -n is _not_ specified, then a regular file-to-file copy is done. - */ -int main(int argc, const char **argv) -{ - int commits; - const char **write_ref = NULL; - char **commit_id; - int arg = 1; - - setup_git_directory(); - git_config(git_default_config); - - while (arg < argc && argv[arg][0] == '-') { - if (argv[arg][1] == 't') - get_tree = 1; - else if (argv[arg][1] == 'c') - get_history = 1; - else if (argv[arg][1] == 'a') { - get_all = 1; - get_tree = 1; - get_history = 1; - } - else if (argv[arg][1] == 'l') - use_link = 1; - else if (argv[arg][1] == 's') - use_symlink = 1; - else if (argv[arg][1] == 'n') - use_filecopy = 0; - else if (argv[arg][1] == 'v') - get_verbosely = 1; - else if (argv[arg][1] == 'w') - write_ref = &argv[++arg]; - else if (!strcmp(argv[arg], "--recover")) - get_recover = 1; - else if (!strcmp(argv[arg], "--stdin")) - commits_on_stdin = 1; - else - usage(local_pull_usage); - arg++; - } - if (argc < arg + 2 - commits_on_stdin) - usage(local_pull_usage); - if (commits_on_stdin) { - commits = pull_targets_stdin(&commit_id, &write_ref); - } else { - commit_id = (char **) &argv[arg++]; - commits = 1; - } - path = argv[arg]; - - if (pull(commits, commit_id, write_ref, path)) - return 1; - - if (commits_on_stdin) - pull_targets_free(commits, commit_id, write_ref); - - return 0; -} diff --git a/lockfile.c b/lockfile.c index 9a1f64d8d7..f45d3ed544 100644 --- a/lockfile.c +++ b/lockfile.c @@ -12,8 +12,10 @@ static void remove_lock_file(void) while (lock_file_list) { if (lock_file_list->owner == me && - lock_file_list->filename[0]) + lock_file_list->filename[0]) { + close(lock_file_list->fd); unlink(lock_file_list->filename); + } lock_file_list = lock_file_list->next; } } @@ -92,7 +94,7 @@ static char *resolve_symlink(char *p, size_t s) return p; } - if (link[0] == '/') { + if (is_absolute_path(link)) { /* absolute path simply replaces p */ if (link_len < s) strcpy(p, link); @@ -120,8 +122,6 @@ static char *resolve_symlink(char *p, size_t s) static int lock_file(struct lock_file *lk, const char *path) { - int fd; - if (strlen(path) >= sizeof(lk->filename)) return -1; strcpy(lk->filename, path); /* @@ -130,8 +130,8 @@ static int lock_file(struct lock_file *lk, const char *path) */ resolve_symlink(lk->filename, sizeof(lk->filename)-5); strcat(lk->filename, ".lock"); - fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); - if (0 <= fd) { + lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); + if (0 <= lk->fd) { if (!lock_file_list) { signal(SIGINT, remove_lock_file_on_signal); atexit(remove_lock_file); @@ -148,7 +148,7 @@ static int lock_file(struct lock_file *lk, const char *path) } else lk->filename[0] = 0; - return fd; + return lk->fd; } int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on_error) @@ -163,6 +163,7 @@ int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; int i; + close(lk->fd); strcpy(result_file, lk->filename); i = strlen(result_file) - 5; /* .lock */ result_file[i] = 0; @@ -194,7 +195,9 @@ int commit_locked_index(struct lock_file *lk) void rollback_lock_file(struct lock_file *lk) { - if (lk->filename[0]) + if (lk->filename[0]) { + close(lk->fd); unlink(lk->filename); + } lk->filename[0] = 0; } diff --git a/log-tree.c b/log-tree.c index 9ebc24b687..1f3fcf16ad 100644 --- a/log-tree.c +++ b/log-tree.c @@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev) } } -static void show_decorations(struct commit *commit) +void show_decorations(struct commit *commit) { const char *prefix; struct name_decoration *decoration; @@ -79,25 +79,14 @@ static int detect_any_signoff(char *letter, int size) return seen_head && seen_name; } -static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p, - unsigned long at, const char *signoff) +static void append_signoff(struct strbuf *sb, const char *signoff) { static const char signed_off_by[] = "Signed-off-by: "; size_t signoff_len = strlen(signoff); int has_signoff = 0; char *cp; - char *buf; - unsigned long buf_sz; - - buf = *buf_p; - buf_sz = *buf_sz_p; - if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) { - buf_sz += strlen(signed_off_by) + signoff_len + 3; - buf = xrealloc(buf, buf_sz); - *buf_p = buf; - *buf_sz_p = buf_sz; - } - cp = buf; + + cp = sb->buf; /* First see if we already have the sign-off by the signer */ while ((cp = strstr(cp, signed_off_by))) { @@ -105,29 +94,25 @@ static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p, has_signoff = 1; cp += strlen(signed_off_by); - if (cp + signoff_len >= buf + at) + if (cp + signoff_len >= sb->buf + sb->len) break; if (strncmp(cp, signoff, signoff_len)) continue; if (!isspace(cp[signoff_len])) continue; /* we already have him */ - return at; + return; } if (!has_signoff) - has_signoff = detect_any_signoff(buf, at); + has_signoff = detect_any_signoff(sb->buf, sb->len); if (!has_signoff) - buf[at++] = '\n'; - - strcpy(buf + at, signed_off_by); - at += strlen(signed_off_by); - strcpy(buf + at, signoff); - at += signoff_len; - buf[at++] = '\n'; - buf[at] = 0; - return at; + strbuf_addch(sb, '\n'); + + strbuf_addstr(sb, signed_off_by); + strbuf_add(sb, signoff, signoff_len); + strbuf_addch(sb, '\n'); } static unsigned int digits_in_number(unsigned int number) @@ -154,14 +139,12 @@ static int has_non_ascii(const char *s) void show_log(struct rev_info *opt, const char *sep) { - char *msgbuf = NULL; - unsigned long msgbuf_len = 0; + struct strbuf msgbuf; struct log_info *log = opt->loginfo; struct commit *commit = log->commit, *parent = log->parent; int abbrev = opt->diffopt.abbrev; int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; const char *extra; - int len; const char *subject = NULL, *extra_headers = opt->extra_headers; opt->loginfo = NULL; @@ -262,8 +245,7 @@ void show_log(struct rev_info *opt, const char *sep) opt->diffopt.stat_sep = buffer; } } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { - fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT), - stdout); + fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) fputs("commit ", stdout); if (commit->object.flags & BOUNDARY) @@ -283,8 +265,7 @@ void show_log(struct rev_info *opt, const char *sep) diff_unique_abbrev(parent->object.sha1, abbrev_commit)); show_decorations(commit); - printf("%s", - diff_get_color(opt->diffopt.color_diff, DIFF_RESET)); + printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET)); putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); if (opt->reflog_info) { show_reflog_message(opt->reflog_info, @@ -300,19 +281,19 @@ void show_log(struct rev_info *opt, const char *sep) /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, - &msgbuf, &msgbuf_len, abbrev, subject, - extra_headers, opt->date_mode, - has_non_ascii(opt->add_signoff)); + strbuf_init(&msgbuf, 0); + pretty_print_commit(opt->commit_format, commit, &msgbuf, + abbrev, subject, extra_headers, opt->date_mode, + has_non_ascii(opt->add_signoff)); if (opt->add_signoff) - len = append_signoff(&msgbuf, &msgbuf_len, len, - opt->add_signoff); + append_signoff(&msgbuf, opt->add_signoff); if (opt->show_log_size) - printf("log size %i\n", len); + printf("log size %i\n", (int)msgbuf.len); - printf("%s%s%s", msgbuf, extra, sep); - free(msgbuf); + if (msgbuf.len) + printf("%s%s%s", msgbuf.buf, extra, sep); + strbuf_release(&msgbuf); } int log_tree_diff_flush(struct rev_info *opt) diff --git a/log-tree.h b/log-tree.h index e82b56a20d..b33f7cd7ac 100644 --- a/log-tree.h +++ b/log-tree.h @@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt, const char *sep); +void show_decorations(struct commit *commit); #endif diff --git a/match-trees.c b/match-trees.c index d7e29c4d1d..0fd6df7d6e 100644 --- a/match-trees.c +++ b/match-trees.c @@ -132,7 +132,7 @@ static void match_trees(const unsigned char *hash1, const unsigned char *hash2, int *best_score, char **best_match, - char *base, + const char *base, int recurse_limit) { struct tree_desc one; diff --git a/merge-recursive.c b/merge-recursive.c index fedfff4aed..9a1e2f269d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -85,12 +85,6 @@ struct stage_data unsigned processed:1; }; -struct output_buffer -{ - struct output_buffer *next; - char *str; -}; - static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; @@ -98,51 +92,52 @@ static int call_depth = 0; static int verbosity = 2; static int rename_limit = -1; static int buffer_output = 1; -static struct output_buffer *output_list, *output_end; +static struct strbuf obuf = STRBUF_INIT; -static int show (int v) +static int show(int v) { return (!call_depth && verbosity >= v) || verbosity >= 5; } -static void output(int v, const char *fmt, ...) +static void flush_output(void) { - va_list args; - va_start(args, fmt); - if (buffer_output && show(v)) { - struct output_buffer *b = xmalloc(sizeof(*b)); - nfvasprintf(&b->str, fmt, args); - b->next = NULL; - if (output_end) - output_end->next = b; - else - output_list = b; - output_end = b; - } else if (show(v)) { - int i; - for (i = call_depth; i--;) - fputs(" ", stdout); - vfprintf(stdout, fmt, args); - fputc('\n', stdout); + if (obuf.len) { + fputs(obuf.buf, stdout); + strbuf_reset(&obuf); } - va_end(args); } -static void flush_output(void) +static void output(int v, const char *fmt, ...) { - struct output_buffer *b, *n; - for (b = output_list; b; b = n) { - int i; - for (i = call_depth; i--;) - fputs(" ", stdout); - fputs(b->str, stdout); - fputc('\n', stdout); - n = b->next; - free(b->str); - free(b); + int len; + va_list ap; + + if (!show(v)) + return; + + strbuf_grow(&obuf, call_depth * 2 + 2); + memset(obuf.buf + obuf.len, ' ', call_depth * 2); + strbuf_setlen(&obuf, obuf.len + call_depth * 2); + + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + + if (len < 0) + len = 0; + if (len >= strbuf_avail(&obuf)) { + strbuf_grow(&obuf, len + 2); + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&obuf)) { + die("this should not happen, your snprintf is broken"); + } } - output_list = NULL; - output_end = NULL; + strbuf_setlen(&obuf, obuf.len + len); + strbuf_add(&obuf, "\n", 1); + if (!buffer_output) + flush_output(); } static void output_commit_title(struct commit *commit) @@ -371,7 +366,7 @@ static struct path_list *get_renames(struct tree *tree, renames = xcalloc(1, sizeof(struct path_list)); diff_setup(&opts); - opts.recursive = 1; + DIFF_OPT_SET(&opts, RECURSIVE); opts.detect_rename = DIFF_DETECT_RENAME; opts.rename_limit = rename_limit; opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -434,19 +429,15 @@ static int update_stages(const char *path, struct diff_filespec *o, static int remove_path(const char *name) { - int ret, len; + int ret; char *slash, *dirs; ret = unlink(name); if (ret) return ret; - len = strlen(name); - dirs = xmalloc(len+1); - memcpy(dirs, name, len); - dirs[len] = '\0'; + dirs = xstrdup(name); while ((slash = strrchr(name, '/'))) { *slash = '\0'; - len = slash - name; if (rmdir(name) != 0) break; } @@ -580,9 +571,7 @@ static void update_file_flags(const unsigned char *sha, flush_buffer(fd, buf, size); close(fd); } else if (S_ISLNK(mode)) { - char *lnk = xmalloc(size + 1); - memcpy(lnk, buf, size); - lnk[size] = '\0'; + char *lnk = xmemdupz(buf, size); mkdir_p(path, 0777); unlink(path); symlink(lnk, path); @@ -874,14 +863,9 @@ static int read_merge_config(const char *var, const char *value) if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) break; if (!fn) { - char *namebuf; fn = xcalloc(1, sizeof(struct ll_merge_driver)); - namebuf = xmalloc(namelen + 1); - memcpy(namebuf, name, namelen); - namebuf[namelen] = 0; - fn->name = namebuf; + fn->name = xmemdupz(name, namelen); fn->fn = ll_ext_merge; - fn->next = NULL; *ll_user_merge_tail = fn; ll_user_merge_tail = &(fn->next); } @@ -111,8 +111,7 @@ static int verify_tag(char *buffer, unsigned long size) int main(int argc, char **argv) { - unsigned long size = 4096; - char *buffer = xmalloc(size); + struct strbuf buf; unsigned char result_sha1[20]; if (argc != 1) @@ -120,21 +119,20 @@ int main(int argc, char **argv) setup_git_directory(); - if (read_fd(0, &buffer, &size)) { - free(buffer); + strbuf_init(&buf, 0); + if (strbuf_read(&buf, 0, 4096) < 0) { die("could not read from stdin"); } /* Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger " */ - if (verify_tag(buffer, size) < 0) + if (verify_tag(buf.buf, buf.len) < 0) die("invalid tag signature file"); - if (write_sha1_file(buffer, size, tag_type, result_sha1) < 0) + if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0) die("unable to write tag file"); - free(buffer); - + strbuf_release(&buf); printf("%s\n", sha1_to_hex(result_sha1)); return 0; } @@ -4,7 +4,6 @@ * Copyright (c) Junio C Hamano, 2006 */ #include "cache.h" -#include "strbuf.h" #include "quote.h" #include "tree.h" @@ -44,30 +43,22 @@ static int ent_compare(const void *a_, const void *b_) static void write_tree(unsigned char *sha1) { - char *buffer; - unsigned long size, offset; + struct strbuf buf; + size_t size; int i; qsort(entries, used, sizeof(*entries), ent_compare); for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; - buffer = xmalloc(size); - offset = 0; + strbuf_init(&buf, size); for (i = 0; i < used; i++) { struct treeent *ent = entries[i]; - - if (offset + ent->len + 100 < size) { - size = alloc_nr(offset + ent->len + 100); - buffer = xrealloc(buffer, size); - } - offset += sprintf(buffer + offset, "%o ", ent->mode); - offset += sprintf(buffer + offset, "%s", ent->name); - buffer[offset++] = 0; - hashcpy((unsigned char*)buffer + offset, ent->sha1); - offset += 20; + strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); + strbuf_add(&buf, ent->sha1, 20); } - write_sha1_file(buffer, offset, tree_type, sha1); + + write_sha1_file(buf.buf, buf.len, tree_type, sha1); } static const char mktree_usage[] = "git-mktree [-z]"; @@ -75,6 +66,7 @@ static const char mktree_usage[] = "git-mktree [-z]"; int main(int ac, char **av) { struct strbuf sb; + struct strbuf p_uq; unsigned char sha1[20]; int line_termination = '\n'; @@ -90,18 +82,14 @@ int main(int ac, char **av) av++; } - strbuf_init(&sb); - while (1) { - int len; + strbuf_init(&sb, 0); + strbuf_init(&p_uq, 0); + while (strbuf_getline(&sb, stdin, line_termination) != EOF) { char *ptr, *ntr; unsigned mode; enum object_type type; char *path; - read_line(&sb, stdin, line_termination); - if (sb.eof) - break; - len = sb.len; ptr = sb.buf; /* Input is non-recursive ls-tree output format * mode SP type SP sha1 TAB name @@ -111,7 +99,7 @@ int main(int ac, char **av) die("input format error: %s", sb.buf); ptr = ntr + 1; /* type */ ntr = strchr(ptr, ' '); - if (!ntr || sb.buf + len <= ntr + 41 || + if (!ntr || sb.buf + sb.len <= ntr + 40 || ntr[41] != '\t' || get_sha1_hex(ntr + 1, sha1)) die("input format error: %s", sb.buf); @@ -121,17 +109,21 @@ int main(int ac, char **av) *ntr++ = 0; /* now at the beginning of SHA1 */ if (type != type_from_string(ptr)) die("object type %s mismatch (%s)", ptr, typename(type)); - ntr += 41; /* at the beginning of name */ - if (line_termination && ntr[0] == '"') - path = unquote_c_style(ntr, NULL); - else - path = ntr; - append_to_tree(mode, sha1, path); + path = ntr + 41; /* at the beginning of name */ + if (line_termination && path[0] == '"') { + strbuf_reset(&p_uq); + if (unquote_c_style(&p_uq, path, NULL)) { + die("invalid quoting"); + } + path = p_uq.buf; + } - if (path != ntr) - free(path); + append_to_tree(mode, sha1, path); } + strbuf_release(&p_uq); + strbuf_release(&sb); + write_tree(sha1); puts(sha1_to_hex(sha1)); exit(0); diff --git a/pack-write.c b/pack-write.c index e59b197e5e..665e2b29b8 100644 --- a/pack-write.c +++ b/pack-write.c @@ -17,7 +17,8 @@ static int sha1_compare(const void *_a, const void *_b) * the SHA1 hash of sorted object names. The objects array passed in * will be sorted by SHA1 on exit. */ -const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1) +char *write_idx_file(char *index_name, struct pack_idx_entry **objects, + int nr_objects, unsigned char *sha1) { struct sha1file *f; struct pack_idx_entry **sorted_by_sha, **list, **last; @@ -179,3 +180,29 @@ void fixup_pack_header_footer(int pack_fd, SHA1_Final(pack_file_sha1, &c); write_or_die(pack_fd, pack_file_sha1, 20); } + +char *index_pack_lockfile(int ip_out) +{ + int len, s; + char packname[46]; + + /* + * The first thing we expects from index-pack's output + * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where + * %40s is the newly created pack SHA1 name. In the "keep" + * case, we need it to remove the corresponding .keep file + * later on. If we don't get that then tough luck with it. + */ + for (len = 0; + len < 46 && (s = xread(ip_out, packname+len, 46-len)) > 0; + len += s); + if (len == 46 && packname[45] == '\n' && + memcmp(packname, "keep\t", 5) == 0) { + char path[PATH_MAX]; + packname[45] = 0; + snprintf(path, sizeof(path), "%s/pack/pack-%s.keep", + get_object_directory(), packname + 5); + return xstrdup(path); + } + return NULL; +} @@ -55,10 +55,11 @@ struct pack_idx_entry { off_t offset; }; -extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); +extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int verify_pack(struct packed_git *, int); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t); +extern char *index_pack_lockfile(int fd); #define PH_ERROR_EOF (-1) #define PH_ERROR_PACK_SIGNATURE (-2) @@ -1,7 +1,5 @@ #include "cache.h" -#include <sys/select.h> - /* * This is split up from the rest of git so that we might do * something different on Windows, for example. diff --git a/parse-options.c b/parse-options.c new file mode 100644 index 0000000000..d3e608ac45 --- /dev/null +++ b/parse-options.c @@ -0,0 +1,358 @@ +#include "git-compat-util.h" +#include "parse-options.h" + +#define OPT_SHORT 1 +#define OPT_UNSET 2 + +struct optparse_t { + const char **argv; + int argc; + const char *opt; +}; + +static inline const char *get_arg(struct optparse_t *p) +{ + if (p->opt) { + const char *res = p->opt; + p->opt = NULL; + return res; + } + p->argc--; + return *++p->argv; +} + +static inline const char *skip_prefix(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int opterror(const struct option *opt, const char *reason, int flags) +{ + if (flags & OPT_SHORT) + return error("switch `%c' %s", opt->short_name, reason); + if (flags & OPT_UNSET) + return error("option `no-%s' %s", opt->long_name, reason); + return error("option `%s' %s", opt->long_name, reason); +} + +static int get_value(struct optparse_t *p, + const struct option *opt, int flags) +{ + const char *s, *arg; + const int unset = flags & OPT_UNSET; + + if (unset && p->opt) + return opterror(opt, "takes no value", flags); + if (unset && (opt->flags & PARSE_OPT_NONEG)) + return opterror(opt, "isn't available", flags); + + if (!(flags & OPT_SHORT) && p->opt) { + switch (opt->type) { + case OPTION_CALLBACK: + if (!(opt->flags & PARSE_OPT_NOARG)) + break; + /* FALLTHROUGH */ + case OPTION_BOOLEAN: + case OPTION_BIT: + case OPTION_SET_INT: + case OPTION_SET_PTR: + return opterror(opt, "takes no value", flags); + default: + break; + } + } + + arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL); + switch (opt->type) { + case OPTION_BIT: + if (unset) + *(int *)opt->value &= ~opt->defval; + else + *(int *)opt->value |= opt->defval; + return 0; + + case OPTION_BOOLEAN: + *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; + return 0; + + case OPTION_SET_INT: + *(int *)opt->value = unset ? 0 : opt->defval; + return 0; + + case OPTION_SET_PTR: + *(void **)opt->value = unset ? NULL : (void *)opt->defval; + return 0; + + case OPTION_STRING: + if (unset) { + *(const char **)opt->value = NULL; + return 0; + } + if (opt->flags & PARSE_OPT_OPTARG && (!arg || *arg == '-')) { + *(const char **)opt->value = (const char *)opt->defval; + return 0; + } + if (!arg) + return opterror(opt, "requires a value", flags); + *(const char **)opt->value = get_arg(p); + return 0; + + case OPTION_CALLBACK: + if (unset) + return (*opt->callback)(opt, NULL, 1); + if (opt->flags & PARSE_OPT_NOARG) + return (*opt->callback)(opt, NULL, 0); + if (opt->flags & PARSE_OPT_OPTARG && (!arg || *arg == '-')) + return (*opt->callback)(opt, NULL, 0); + if (!arg) + return opterror(opt, "requires a value", flags); + return (*opt->callback)(opt, get_arg(p), 0); + + case OPTION_INTEGER: + if (unset) { + *(int *)opt->value = 0; + return 0; + } + if (opt->flags & PARSE_OPT_OPTARG && (!arg || !isdigit(*arg))) { + *(int *)opt->value = opt->defval; + return 0; + } + if (!arg) + return opterror(opt, "requires a value", flags); + *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10); + if (*s) + return opterror(opt, "expects a numerical value", flags); + return 0; + + default: + die("should not happen, someone must be hit on the forehead"); + } +} + +static int parse_short_opt(struct optparse_t *p, const struct option *options) +{ + for (; options->type != OPTION_END; options++) { + if (options->short_name == *p->opt) { + p->opt = p->opt[1] ? p->opt + 1 : NULL; + return get_value(p, options, OPT_SHORT); + } + } + return error("unknown switch `%c'", *p->opt); +} + +static int parse_long_opt(struct optparse_t *p, const char *arg, + const struct option *options) +{ + const char *arg_end = strchr(arg, '='); + const struct option *abbrev_option = NULL, *ambiguous_option = NULL; + int abbrev_flags = 0, ambiguous_flags = 0; + + if (!arg_end) + arg_end = arg + strlen(arg); + + for (; options->type != OPTION_END; options++) { + const char *rest; + int flags = 0; + + if (!options->long_name) + continue; + + rest = skip_prefix(arg, options->long_name); + if (!rest) { + /* abbreviated? */ + if (!strncmp(options->long_name, arg, arg_end - arg)) { +is_abbreviated: + if (abbrev_option) { + /* + * If this is abbreviated, it is + * ambiguous. So when there is no + * exact match later, we need to + * error out. + */ + ambiguous_option = abbrev_option; + ambiguous_flags = abbrev_flags; + } + if (!(flags & OPT_UNSET) && *arg_end) + p->opt = arg_end + 1; + abbrev_option = options; + abbrev_flags = flags; + continue; + } + /* negated and abbreviated very much? */ + if (!prefixcmp("no-", arg)) { + flags |= OPT_UNSET; + goto is_abbreviated; + } + /* negated? */ + if (strncmp(arg, "no-", 3)) + continue; + flags |= OPT_UNSET; + rest = skip_prefix(arg + 3, options->long_name); + /* abbreviated and negated? */ + if (!rest && !prefixcmp(options->long_name, arg + 3)) + goto is_abbreviated; + if (!rest) + continue; + } + if (*rest) { + if (*rest != '=') + continue; + p->opt = rest + 1; + } + return get_value(p, options, flags); + } + + if (ambiguous_option) + return error("Ambiguous option: %s " + "(could be --%s%s or --%s%s)", + arg, + (ambiguous_flags & OPT_UNSET) ? "no-" : "", + ambiguous_option->long_name, + (abbrev_flags & OPT_UNSET) ? "no-" : "", + abbrev_option->long_name); + if (abbrev_option) + return get_value(p, abbrev_option, abbrev_flags); + return error("unknown option `%s'", arg); +} + +int parse_options(int argc, const char **argv, const struct option *options, + const char * const usagestr[], int flags) +{ + struct optparse_t args = { argv + 1, argc - 1, NULL }; + int j = 0; + + for (; args.argc; args.argc--, args.argv++) { + const char *arg = args.argv[0]; + + if (*arg != '-' || !arg[1]) { + argv[j++] = args.argv[0]; + continue; + } + + if (arg[1] != '-') { + args.opt = arg + 1; + do { + if (*args.opt == 'h') + usage_with_options(usagestr, options); + if (parse_short_opt(&args, options) < 0) + usage_with_options(usagestr, options); + } while (args.opt); + continue; + } + + if (!arg[2]) { /* "--" */ + if (!(flags & PARSE_OPT_KEEP_DASHDASH)) { + args.argc--; + args.argv++; + } + break; + } + + if (!strcmp(arg + 2, "help")) + usage_with_options(usagestr, options); + if (parse_long_opt(&args, arg + 2, options)) + usage_with_options(usagestr, options); + } + + memmove(argv + j, args.argv, args.argc * sizeof(*argv)); + argv[j + args.argc] = NULL; + return j + args.argc; +} + +#define USAGE_OPTS_WIDTH 24 +#define USAGE_GAP 2 + +void usage_with_options(const char * const *usagestr, + const struct option *opts) +{ + fprintf(stderr, "usage: %s\n", *usagestr++); + while (*usagestr && **usagestr) + fprintf(stderr, " or: %s\n", *usagestr++); + while (*usagestr) + fprintf(stderr, " %s\n", *usagestr++); + + if (opts->type != OPTION_GROUP) + fputc('\n', stderr); + + for (; opts->type != OPTION_END; opts++) { + size_t pos; + int pad; + + if (opts->type == OPTION_GROUP) { + fputc('\n', stderr); + if (*opts->help) + fprintf(stderr, "%s\n", opts->help); + continue; + } + + pos = fprintf(stderr, " "); + if (opts->short_name) + pos += fprintf(stderr, "-%c", opts->short_name); + if (opts->long_name && opts->short_name) + pos += fprintf(stderr, ", "); + if (opts->long_name) + pos += fprintf(stderr, "--%s", opts->long_name); + + switch (opts->type) { + case OPTION_INTEGER: + if (opts->flags & PARSE_OPT_OPTARG) + pos += fprintf(stderr, " [<n>]"); + else + pos += fprintf(stderr, " <n>"); + break; + case OPTION_CALLBACK: + if (opts->flags & PARSE_OPT_NOARG) + break; + /* FALLTHROUGH */ + case OPTION_STRING: + if (opts->argh) { + if (opts->flags & PARSE_OPT_OPTARG) + pos += fprintf(stderr, " [<%s>]", opts->argh); + else + pos += fprintf(stderr, " <%s>", opts->argh); + } else { + if (opts->flags & PARSE_OPT_OPTARG) + pos += fprintf(stderr, " [...]"); + else + pos += fprintf(stderr, " ..."); + } + break; + default: /* OPTION_{BIT,BOOLEAN,SET_INT,SET_PTR} */ + break; + } + + if (pos <= USAGE_OPTS_WIDTH) + pad = USAGE_OPTS_WIDTH - pos; + else { + fputc('\n', stderr); + pad = USAGE_OPTS_WIDTH; + } + fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help); + } + fputc('\n', stderr); + + exit(129); +} + +/*----- some often used options -----*/ +#include "cache.h" + +int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset) +{ + int v; + + if (!arg) { + v = unset ? 0 : DEFAULT_ABBREV; + } else { + v = strtol(arg, (char **)&arg, 10); + if (*arg) + return opterror(opt, "expects a numerical value", 0); + if (v && v < MINIMUM_ABBREV) + v = MINIMUM_ABBREV; + else if (v > 40) + v = 40; + } + *(int *)(opt->value) = v; + return 0; +} diff --git a/parse-options.h b/parse-options.h new file mode 100644 index 0000000000..a8760ac4b2 --- /dev/null +++ b/parse-options.h @@ -0,0 +1,116 @@ +#ifndef PARSE_OPTIONS_H +#define PARSE_OPTIONS_H + +enum parse_opt_type { + /* special types */ + OPTION_END, + OPTION_GROUP, + /* options with no arguments */ + OPTION_BIT, + OPTION_BOOLEAN, /* _INCR would have been a better name */ + OPTION_SET_INT, + OPTION_SET_PTR, + /* options with arguments (usually) */ + OPTION_STRING, + OPTION_INTEGER, + OPTION_CALLBACK, +}; + +enum parse_opt_flags { + PARSE_OPT_KEEP_DASHDASH = 1, +}; + +enum parse_opt_option_flags { + PARSE_OPT_OPTARG = 1, + PARSE_OPT_NOARG = 2, + PARSE_OPT_NONEG = 4, +}; + +struct option; +typedef int parse_opt_cb(const struct option *, const char *arg, int unset); + +/* + * `type`:: + * holds the type of the option, you must have an OPTION_END last in your + * array. + * + * `short_name`:: + * the character to use as a short option name, '\0' if none. + * + * `long_name`:: + * the long option name, without the leading dashes, NULL if none. + * + * `value`:: + * stores pointers to the values to be filled. + * + * `argh`:: + * token to explain the kind of argument this option wants. Keep it + * homogenous across the repository. + * + * `help`:: + * the short help associated to what the option does. + * Must never be NULL (except for OPTION_END). + * OPTION_GROUP uses this pointer to store the group header. + * + * `flags`:: + * mask of parse_opt_option_flags. + * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs) + * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs + * PARSE_OPT_NONEG: says that this option cannot be negated + * + * `callback`:: + * pointer to the callback to use for OPTION_CALLBACK. + * + * `defval`:: + * default value to fill (*->value) with for PARSE_OPT_OPTARG. + * OPTION_{BIT,SET_INT,SET_PTR} store the {mask,integer,pointer} to put in + * the value when met. + * CALLBACKS can use it like they want. + */ +struct option { + enum parse_opt_type type; + int short_name; + const char *long_name; + void *value; + const char *argh; + const char *help; + + int flags; + parse_opt_cb *callback; + intptr_t defval; +}; + +#define OPT_END() { OPTION_END } +#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } +#define OPT_BIT(s, l, v, h, b) { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) } +#define OPT_BOOLEAN(s, l, v, h) { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) } +#define OPT_SET_INT(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, (h), 0, NULL, (i) } +#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, (h), 0, NULL, (p) } +#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), NULL, (h) } +#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_CALLBACK(s, l, v, a, h, f) \ + { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) } + +/* parse_options() will filter out the processed options and leave the + * non-option argments in argv[]. + * Returns the number of arguments left in argv[]. + */ +extern int parse_options(int argc, const char **argv, + const struct option *options, + const char * const usagestr[], int flags); + +extern NORETURN void usage_with_options(const char * const *usagestr, + const struct option *options); + +/*----- some often used options -----*/ +extern int parse_opt_abbrev_cb(const struct option *, const char *, int); + +#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") +#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") +#define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run") +#define OPT__ABBREV(var) \ + { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ + "use <n> digits to display SHA-1s", \ + PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } + +#endif diff --git a/patch-ids.c b/patch-ids.c index a288fac992..3be5d3165e 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -121,7 +121,7 @@ int init_patch_ids(struct patch_ids *ids) { memset(ids, 0, sizeof(*ids)); diff_setup(&ids->diffopts); - ids->diffopts.recursive = 1; + DIFF_OPT_SET(&ids->diffopts, RECURSIVE); if (diff_setup_done(&ids->diffopts) < 0) return error("diff_setup_done failed"); return 0; diff --git a/peek-remote.c b/peek-remote.c deleted file mode 100644 index ceb787170e..0000000000 --- a/peek-remote.c +++ /dev/null @@ -1,75 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "pkt-line.h" - -static const char peek_remote_usage[] = -"git-peek-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>"; -static const char *uploadpack = "git-upload-pack"; - -static int peek_remote(int fd[2], unsigned flags) -{ - struct ref *ref; - - get_remote_heads(fd[0], &ref, 0, NULL, flags); - packet_flush(fd[1]); - - while (ref) { - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - return 0; -} - -int main(int argc, char **argv) -{ - int i, ret; - char *dest = NULL; - int fd[2]; - pid_t pid; - int nongit = 0; - unsigned flags = 0; - - setup_git_directory_gently(&nongit); - - for (i = 1; i < argc; i++) { - char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - uploadpack = arg + 7; - continue; - } - if (!strcmp("--tags", arg)) { - flags |= REF_TAGS; - continue; - } - if (!strcmp("--heads", arg)) { - flags |= REF_HEADS; - continue; - } - if (!strcmp("--refs", arg)) { - flags |= REF_NORMAL; - continue; - } - usage(peek_remote_usage); - } - dest = arg; - break; - } - - if (!dest || i != argc - 1) - usage(peek_remote_usage); - - pid = git_connect(fd, dest, uploadpack, 0); - if (pid < 0) - return 1; - ret = peek_remote(fd, flags); - close(fd[0]); - close(fd[1]); - ret |= finish_connect(pid); - return !!ret; -} diff --git a/pretty.c b/pretty.c new file mode 100644 index 0000000000..9db75b4e4f --- /dev/null +++ b/pretty.c @@ -0,0 +1,825 @@ +#include "cache.h" +#include "commit.h" +#include "utf8.h" +#include "diff.h" +#include "revision.h" + +static struct cmt_fmt_map { + const char *n; + size_t cmp_len; + enum cmit_fmt v; +} cmt_fmts[] = { + { "raw", 1, CMIT_FMT_RAW }, + { "medium", 1, CMIT_FMT_MEDIUM }, + { "short", 1, CMIT_FMT_SHORT }, + { "email", 1, CMIT_FMT_EMAIL }, + { "full", 5, CMIT_FMT_FULL }, + { "fuller", 5, CMIT_FMT_FULLER }, + { "oneline", 1, CMIT_FMT_ONELINE }, + { "format:", 7, CMIT_FMT_USERFORMAT}, +}; + +static char *user_format; + +enum cmit_fmt get_commit_format(const char *arg) +{ + int i; + + if (!arg || !*arg) + return CMIT_FMT_DEFAULT; + if (*arg == '=') + arg++; + if (!prefixcmp(arg, "format:")) { + if (user_format) + free(user_format); + user_format = xstrdup(arg + 7); + return CMIT_FMT_USERFORMAT; + } + for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { + if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) && + !strncmp(arg, cmt_fmts[i].n, strlen(arg))) + return cmt_fmts[i].v; + } + + die("invalid --pretty format: %s", arg); +} + +/* + * Generic support for pretty-printing the header + */ +static int get_one_line(const char *msg) +{ + int ret = 0; + + for (;;) { + char c = *msg++; + if (!c) + break; + ret++; + if (c == '\n') + break; + } + return ret; +} + +/* High bit set, or ISO-2022-INT */ +int non_ascii(int ch) +{ + ch = (ch & 0xff); + return ((ch & 0x80) || (ch == 0x1b)); +} + +static int is_rfc2047_special(char ch) +{ + return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_')); +} + +static void add_rfc2047(struct strbuf *sb, const char *line, int len, + const char *encoding) +{ + int i, last; + + for (i = 0; i < len; i++) { + int ch = line[i]; + if (non_ascii(ch)) + goto needquote; + if ((i + 1 < len) && (ch == '=' && line[i+1] == '?')) + goto needquote; + } + strbuf_add(sb, line, len); + return; + +needquote: + strbuf_grow(sb, len * 3 + strlen(encoding) + 100); + strbuf_addf(sb, "=?%s?q?", encoding); + for (i = last = 0; i < len; i++) { + unsigned ch = line[i] & 0xFF; + /* + * We encode ' ' using '=20' even though rfc2047 + * allows using '_' for readability. Unfortunately, + * many programs do not understand this and just + * leave the underscore in place. + */ + if (is_rfc2047_special(ch) || ch == ' ') { + strbuf_add(sb, line + last, i - last); + strbuf_addf(sb, "=%02X", ch); + last = i + 1; + } + } + strbuf_add(sb, line + last, len - last); + strbuf_addstr(sb, "?="); +} + +static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, + const char *line, enum date_mode dmode, + const char *encoding) +{ + char *date; + int namelen; + unsigned long time; + int tz; + const char *filler = " "; + + if (fmt == CMIT_FMT_ONELINE) + return; + date = strchr(line, '>'); + if (!date) + return; + namelen = ++date - line; + time = strtoul(date, &date, 10); + tz = strtol(date, NULL, 10); + + if (fmt == CMIT_FMT_EMAIL) { + char *name_tail = strchr(line, '<'); + int display_name_length; + if (!name_tail) + return; + while (line < name_tail && isspace(name_tail[-1])) + name_tail--; + display_name_length = name_tail - line; + filler = ""; + strbuf_addstr(sb, "From: "); + add_rfc2047(sb, line, display_name_length, encoding); + strbuf_add(sb, name_tail, namelen - display_name_length); + strbuf_addch(sb, '\n'); + } else { + strbuf_addf(sb, "%s: %.*s%.*s\n", what, + (fmt == CMIT_FMT_FULLER) ? 4 : 0, + filler, namelen, line); + } + switch (fmt) { + case CMIT_FMT_MEDIUM: + strbuf_addf(sb, "Date: %s\n", show_date(time, tz, dmode)); + break; + case CMIT_FMT_EMAIL: + strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822)); + break; + case CMIT_FMT_FULLER: + strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode)); + break; + default: + /* notin' */ + break; + } +} + +static int is_empty_line(const char *line, int *len_p) +{ + int len = *len_p; + while (len && isspace(line[len-1])) + len--; + *len_p = len; + return !len; +} + +static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb, + const struct commit *commit, int abbrev) +{ + struct commit_list *parent = commit->parents; + + if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || + !parent || !parent->next) + return; + + strbuf_addstr(sb, "Merge:"); + + while (parent) { + struct commit *p = parent->item; + const char *hex = NULL; + const char *dots; + if (abbrev) + hex = find_unique_abbrev(p->object.sha1, abbrev); + if (!hex) + hex = sha1_to_hex(p->object.sha1); + dots = (abbrev && strlen(hex) != 40) ? "..." : ""; + parent = parent->next; + + strbuf_addf(sb, " %s%s", hex, dots); + } + strbuf_addch(sb, '\n'); +} + +static char *get_header(const struct commit *commit, const char *key) +{ + int key_len = strlen(key); + const char *line = commit->buffer; + + for (;;) { + const char *eol = strchr(line, '\n'), *next; + + if (line == eol) + return NULL; + if (!eol) { + eol = line + strlen(line); + next = NULL; + } else + next = eol + 1; + if (eol - line > key_len && + !strncmp(line, key, key_len) && + line[key_len] == ' ') { + return xmemdupz(line + key_len + 1, eol - line - key_len - 1); + } + line = next; + } +} + +static char *replace_encoding_header(char *buf, const char *encoding) +{ + struct strbuf tmp; + size_t start, len; + char *cp = buf; + + /* guess if there is an encoding header before a \n\n */ + while (strncmp(cp, "encoding ", strlen("encoding "))) { + cp = strchr(cp, '\n'); + if (!cp || *++cp == '\n') + return buf; + } + start = cp - buf; + cp = strchr(cp, '\n'); + if (!cp) + return buf; /* should not happen but be defensive */ + len = cp + 1 - (buf + start); + + strbuf_init(&tmp, 0); + strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1); + if (is_encoding_utf8(encoding)) { + /* we have re-coded to UTF-8; drop the header */ + strbuf_remove(&tmp, start, len); + } else { + /* just replaces XXXX in 'encoding XXXX\n' */ + strbuf_splice(&tmp, start + strlen("encoding "), + len - strlen("encoding \n"), + encoding, strlen(encoding)); + } + return strbuf_detach(&tmp, NULL); +} + +static char *logmsg_reencode(const struct commit *commit, + const char *output_encoding) +{ + static const char *utf8 = "utf-8"; + const char *use_encoding; + char *encoding; + char *out; + + if (!*output_encoding) + return NULL; + encoding = get_header(commit, "encoding"); + use_encoding = encoding ? encoding : utf8; + if (!strcmp(use_encoding, output_encoding)) + if (encoding) /* we'll strip encoding header later */ + out = xstrdup(commit->buffer); + else + return NULL; /* nothing to do */ + else + out = reencode_string(commit->buffer, + output_encoding, use_encoding); + if (out) + out = replace_encoding_header(out, output_encoding); + + free(encoding); + return out; +} + +static void format_person_part(struct strbuf *sb, char part, + const char *msg, int len) +{ + int start, end, tz = 0; + unsigned long date; + char *ep; + + /* parse name */ + for (end = 0; end < len && msg[end] != '<'; end++) + ; /* do nothing */ + start = end + 1; + while (end > 0 && isspace(msg[end - 1])) + end--; + if (part == 'n') { /* name */ + strbuf_add(sb, msg, end); + return; + } + + if (start >= len) + return; + + /* parse email */ + for (end = start + 1; end < len && msg[end] != '>'; end++) + ; /* do nothing */ + + if (end >= len) + return; + + if (part == 'e') { /* email */ + strbuf_add(sb, msg + start, end - start); + return; + } + + /* parse date */ + for (start = end + 1; start < len && isspace(msg[start]); start++) + ; /* do nothing */ + if (start >= len) + return; + date = strtoul(msg + start, &ep, 10); + if (msg + start == ep) + return; + + if (part == 't') { /* date, UNIX timestamp */ + strbuf_add(sb, msg + start, ep - (msg + start)); + return; + } + + /* parse tz */ + for (start = ep - msg + 1; start < len && isspace(msg[start]); start++) + ; /* do nothing */ + if (start + 1 < len) { + tz = strtoul(msg + start + 1, NULL, 10); + if (msg[start] == '-') + tz = -tz; + } + + switch (part) { + case 'd': /* date */ + strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL)); + return; + case 'D': /* date, RFC2822 style */ + strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822)); + return; + case 'r': /* date, relative */ + strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE)); + return; + case 'i': /* date, ISO 8601 */ + strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601)); + return; + } +} + +struct chunk { + size_t off; + size_t len; +}; + +struct format_commit_context { + const struct commit *commit; + + /* These offsets are relative to the start of the commit message. */ + int commit_header_parsed; + struct chunk subject; + struct chunk author; + struct chunk committer; + struct chunk encoding; + size_t body_off; + + /* The following ones are relative to the result struct strbuf. */ + struct chunk abbrev_commit_hash; + struct chunk abbrev_tree_hash; + struct chunk abbrev_parent_hashes; +}; + +static int add_again(struct strbuf *sb, struct chunk *chunk) +{ + if (chunk->len) { + strbuf_adddup(sb, chunk->off, chunk->len); + return 1; + } + + /* + * We haven't seen this chunk before. Our caller is surely + * going to add it the hard way now. Remember the most likely + * start of the to-be-added chunk: the current end of the + * struct strbuf. + */ + chunk->off = sb->len; + return 0; +} + +static void parse_commit_header(struct format_commit_context *context) +{ + const char *msg = context->commit->buffer; + int i; + enum { HEADER, SUBJECT, BODY } state; + + for (i = 0, state = HEADER; msg[i] && state < BODY; i++) { + int eol; + for (eol = i; msg[eol] && msg[eol] != '\n'; eol++) + ; /* do nothing */ + + if (state == SUBJECT) { + context->subject.off = i; + context->subject.len = eol - i; + i = eol; + } + if (i == eol) { + state++; + /* strip empty lines */ + while (msg[eol + 1] == '\n') + eol++; + } else if (!prefixcmp(msg + i, "author ")) { + context->author.off = i + 7; + context->author.len = eol - i - 7; + } else if (!prefixcmp(msg + i, "committer ")) { + context->committer.off = i + 10; + context->committer.len = eol - i - 10; + } else if (!prefixcmp(msg + i, "encoding ")) { + context->encoding.off = i + 9; + context->encoding.len = eol - i - 9; + } + i = eol; + } + context->body_off = i; + context->commit_header_parsed = 1; +} + +static void format_commit_item(struct strbuf *sb, const char *placeholder, + void *context) +{ + struct format_commit_context *c = context; + const struct commit *commit = c->commit; + const char *msg = commit->buffer; + struct commit_list *p; + + /* these are independent of the commit */ + switch (placeholder[0]) { + case 'C': + switch (placeholder[3]) { + case 'd': /* red */ + strbuf_addstr(sb, "\033[31m"); + return; + case 'e': /* green */ + strbuf_addstr(sb, "\033[32m"); + return; + case 'u': /* blue */ + strbuf_addstr(sb, "\033[34m"); + return; + case 's': /* reset color */ + strbuf_addstr(sb, "\033[m"); + return; + } + case 'n': /* newline */ + strbuf_addch(sb, '\n'); + return; + } + + /* these depend on the commit */ + if (!commit->object.parsed) + parse_object(commit->object.sha1); + + switch (placeholder[0]) { + case 'H': /* commit hash */ + strbuf_addstr(sb, sha1_to_hex(commit->object.sha1)); + return; + case 'h': /* abbreviated commit hash */ + if (add_again(sb, &c->abbrev_commit_hash)) + return; + strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, + DEFAULT_ABBREV)); + c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; + return; + case 'T': /* tree hash */ + strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1)); + return; + case 't': /* abbreviated tree hash */ + if (add_again(sb, &c->abbrev_tree_hash)) + return; + strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, + DEFAULT_ABBREV)); + c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; + return; + case 'P': /* parent hashes */ + for (p = commit->parents; p; p = p->next) { + if (p != commit->parents) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1)); + } + return; + case 'p': /* abbreviated parent hashes */ + if (add_again(sb, &c->abbrev_parent_hashes)) + return; + for (p = commit->parents; p; p = p->next) { + if (p != commit->parents) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, find_unique_abbrev( + p->item->object.sha1, DEFAULT_ABBREV)); + } + c->abbrev_parent_hashes.len = sb->len - + c->abbrev_parent_hashes.off; + return; + case 'm': /* left/right/bottom */ + strbuf_addch(sb, (commit->object.flags & BOUNDARY) + ? '-' + : (commit->object.flags & SYMMETRIC_LEFT) + ? '<' + : '>'); + return; + } + + /* For the rest we have to parse the commit header. */ + if (!c->commit_header_parsed) + parse_commit_header(c); + + switch (placeholder[0]) { + case 's': + strbuf_add(sb, msg + c->subject.off, c->subject.len); + return; + case 'a': + format_person_part(sb, placeholder[1], + msg + c->author.off, c->author.len); + return; + case 'c': + format_person_part(sb, placeholder[1], + msg + c->committer.off, c->committer.len); + return; + case 'e': + strbuf_add(sb, msg + c->encoding.off, c->encoding.len); + return; + case 'b': + strbuf_addstr(sb, msg + c->body_off); + return; + } +} + +void format_commit_message(const struct commit *commit, + const void *format, struct strbuf *sb) +{ + const char *placeholders[] = { + "H", /* commit hash */ + "h", /* abbreviated commit hash */ + "T", /* tree hash */ + "t", /* abbreviated tree hash */ + "P", /* parent hashes */ + "p", /* abbreviated parent hashes */ + "an", /* author name */ + "ae", /* author email */ + "ad", /* author date */ + "aD", /* author date, RFC2822 style */ + "ar", /* author date, relative */ + "at", /* author date, UNIX timestamp */ + "ai", /* author date, ISO 8601 */ + "cn", /* committer name */ + "ce", /* committer email */ + "cd", /* committer date */ + "cD", /* committer date, RFC2822 style */ + "cr", /* committer date, relative */ + "ct", /* committer date, UNIX timestamp */ + "ci", /* committer date, ISO 8601 */ + "e", /* encoding */ + "s", /* subject */ + "b", /* body */ + "Cred", /* red */ + "Cgreen", /* green */ + "Cblue", /* blue */ + "Creset", /* reset color */ + "n", /* newline */ + "m", /* left/right/bottom */ + NULL + }; + struct format_commit_context context; + + memset(&context, 0, sizeof(context)); + context.commit = commit; + strbuf_expand(sb, format, placeholders, format_commit_item, &context); +} + +static void pp_header(enum cmit_fmt fmt, + int abbrev, + enum date_mode dmode, + const char *encoding, + const struct commit *commit, + const char **msg_p, + struct strbuf *sb) +{ + int parents_shown = 0; + + for (;;) { + const char *line = *msg_p; + int linelen = get_one_line(*msg_p); + + if (!linelen) + return; + *msg_p += linelen; + + if (linelen == 1) + /* End of header */ + return; + + if (fmt == CMIT_FMT_RAW) { + strbuf_add(sb, line, linelen); + continue; + } + + if (!memcmp(line, "parent ", 7)) { + if (linelen != 48) + die("bad parent line in commit"); + continue; + } + + if (!parents_shown) { + struct commit_list *parent; + int num; + for (parent = commit->parents, num = 0; + parent; + parent = parent->next, num++) + ; + /* with enough slop */ + strbuf_grow(sb, num * 50 + 20); + add_merge_info(fmt, sb, commit, abbrev); + parents_shown = 1; + } + + /* + * MEDIUM == DEFAULT shows only author with dates. + * FULL shows both authors but not dates. + * FULLER shows both authors and dates. + */ + if (!memcmp(line, "author ", 7)) { + strbuf_grow(sb, linelen + 80); + add_user_info("Author", fmt, sb, line + 7, dmode, encoding); + } + if (!memcmp(line, "committer ", 10) && + (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) { + strbuf_grow(sb, linelen + 80); + add_user_info("Commit", fmt, sb, line + 10, dmode, encoding); + } + } +} + +static void pp_title_line(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + const char *subject, + const char *after_subject, + const char *encoding, + int plain_non_ascii) +{ + struct strbuf title; + + strbuf_init(&title, 80); + + for (;;) { + const char *line = *msg_p; + int linelen = get_one_line(line); + + *msg_p += linelen; + if (!linelen || is_empty_line(line, &linelen)) + break; + + strbuf_grow(&title, linelen + 2); + if (title.len) { + if (fmt == CMIT_FMT_EMAIL) { + strbuf_addch(&title, '\n'); + } + strbuf_addch(&title, ' '); + } + strbuf_add(&title, line, linelen); + } + + strbuf_grow(sb, title.len + 1024); + if (subject) { + strbuf_addstr(sb, subject); + add_rfc2047(sb, title.buf, title.len, encoding); + } else { + strbuf_addbuf(sb, &title); + } + strbuf_addch(sb, '\n'); + + if (plain_non_ascii) { + const char *header_fmt = + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=%s\n" + "Content-Transfer-Encoding: 8bit\n"; + strbuf_addf(sb, header_fmt, encoding); + } + if (after_subject) { + strbuf_addstr(sb, after_subject); + } + if (fmt == CMIT_FMT_EMAIL) { + strbuf_addch(sb, '\n'); + } + strbuf_release(&title); +} + +static void pp_remainder(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + int indent) +{ + int first = 1; + for (;;) { + const char *line = *msg_p; + int linelen = get_one_line(line); + *msg_p += linelen; + + if (!linelen) + break; + + if (is_empty_line(line, &linelen)) { + if (first) + continue; + if (fmt == CMIT_FMT_SHORT) + break; + } + first = 0; + + strbuf_grow(sb, linelen + indent + 20); + if (indent) { + memset(sb->buf + sb->len, ' ', indent); + strbuf_setlen(sb, sb->len + indent); + } + strbuf_add(sb, line, linelen); + strbuf_addch(sb, '\n'); + } +} + +void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + struct strbuf *sb, int abbrev, + const char *subject, const char *after_subject, + enum date_mode dmode, int plain_non_ascii) +{ + unsigned long beginning_of_body; + int indent = 4; + const char *msg = commit->buffer; + char *reencoded; + const char *encoding; + + if (fmt == CMIT_FMT_USERFORMAT) { + format_commit_message(commit, user_format, sb); + return; + } + + encoding = (git_log_output_encoding + ? git_log_output_encoding + : git_commit_encoding); + if (!encoding) + encoding = "utf-8"; + reencoded = logmsg_reencode(commit, encoding); + if (reencoded) { + msg = reencoded; + } + + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + indent = 0; + + /* After-subject is used to pass in Content-Type: multipart + * MIME header; in that case we do not have to do the + * plaintext content type even if the commit message has + * non 7-bit ASCII character. Otherwise, check if we need + * to say this is not a 7-bit ASCII. + */ + if (fmt == CMIT_FMT_EMAIL && !after_subject) { + int i, ch, in_body; + + for (in_body = i = 0; (ch = msg[i]); i++) { + if (!in_body) { + /* author could be non 7-bit ASCII but + * the log may be so; skip over the + * header part first. + */ + if (ch == '\n' && msg[i+1] == '\n') + in_body = 1; + } + else if (non_ascii(ch)) { + plain_non_ascii = 1; + break; + } + } + } + + pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb); + if (fmt != CMIT_FMT_ONELINE && !subject) { + strbuf_addch(sb, '\n'); + } + + /* Skip excess blank lines at the beginning of body, if any... */ + for (;;) { + int linelen = get_one_line(msg); + int ll = linelen; + if (!linelen) + break; + if (!is_empty_line(msg, &ll)) + break; + msg += linelen; + } + + /* These formats treat the title line specially. */ + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + pp_title_line(fmt, &msg, sb, subject, + after_subject, encoding, plain_non_ascii); + + beginning_of_body = sb->len; + if (fmt != CMIT_FMT_ONELINE) + pp_remainder(fmt, &msg, sb, indent); + strbuf_rtrim(sb); + + /* Make sure there is an EOLN for the non-oneline case */ + if (fmt != CMIT_FMT_ONELINE) + strbuf_addch(sb, '\n'); + + /* + * The caller may append additional body text in e-mail + * format. Make sure we did not strip the blank line + * between the header and the body. + */ + if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) + strbuf_addch(sb, '\n'); + free(reencoded); +} diff --git a/progress.c b/progress.c index 4344f4eed5..d19f80c0bb 100644 --- a/progress.c +++ b/progress.c @@ -1,6 +1,40 @@ +/* + * Simple text-based progress display module for GIT + * + * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + #include "git-compat-util.h" #include "progress.h" +#define TP_IDX_MAX 8 + +struct throughput { + off_t curr_total; + off_t prev_total; + struct timeval prev_tv; + unsigned int avg_bytes; + unsigned int avg_misecs; + unsigned int last_bytes[TP_IDX_MAX]; + unsigned int last_misecs[TP_IDX_MAX]; + unsigned int idx; + char display[32]; +}; + +struct progress { + const char *title; + int last_value; + unsigned total; + unsigned last_percent; + unsigned delay; + unsigned delayed_percent_treshold; + struct throughput *throughput; +}; + static volatile sig_atomic_t progress_update; static void progress_interval(int signum) @@ -35,10 +69,11 @@ static void clear_progress_signal(void) progress_update = 0; } -int display_progress(struct progress *progress, unsigned n) +static int display(struct progress *progress, unsigned n, const char *done) { + const char *eol, *tp; + if (progress->delay) { - char buf[80]; if (!progress_update || --progress->delay) return 0; if (progress->total) { @@ -51,60 +86,173 @@ int display_progress(struct progress *progress, unsigned n) return 0; } } - if (snprintf(buf, sizeof(buf), - progress->delayed_title, progress->total)) - fprintf(stderr, "%s\n", buf); } + + progress->last_value = n; + tp = (progress->throughput) ? progress->throughput->display : ""; + eol = done ? done : " \r"; if (progress->total) { unsigned percent = n * 100 / progress->total; if (percent != progress->last_percent || progress_update) { progress->last_percent = percent; - fprintf(stderr, "%s%4u%% (%u/%u) done\r", - progress->prefix, percent, n, progress->total); + fprintf(stderr, "%s: %3u%% (%u/%u)%s%s", + progress->title, percent, n, + progress->total, tp, eol); + fflush(stderr); progress_update = 0; - progress->need_lf = 1; return 1; } } else if (progress_update) { - fprintf(stderr, "%s%u\r", progress->prefix, n); + fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol); + fflush(stderr); progress_update = 0; - progress->need_lf = 1; return 1; } + return 0; } -void start_progress(struct progress *progress, const char *title, - const char *prefix, unsigned total) +static void throughput_string(struct throughput *tp, off_t total, + unsigned int rate) { - char buf[80]; - progress->prefix = prefix; - progress->total = total; - progress->last_percent = -1; - progress->delay = 0; - progress->need_lf = 0; - if (snprintf(buf, sizeof(buf), title, total)) - fprintf(stderr, "%s\n", buf); - set_progress_signal(); + int l = sizeof(tp->display); + if (total > 1 << 30) { + l -= snprintf(tp->display, l, ", %u.%2.2u GiB", + (int)(total >> 30), + (int)(total & ((1 << 30) - 1)) / 10737419); + } else if (total > 1 << 20) { + l -= snprintf(tp->display, l, ", %u.%2.2u MiB", + (int)(total >> 20), + ((int)(total & ((1 << 20) - 1)) * 100) >> 20); + } else if (total > 1 << 10) { + l -= snprintf(tp->display, l, ", %u.%2.2u KiB", + (int)(total >> 10), + ((int)(total & ((1 << 10) - 1)) * 100) >> 10); + } else { + l -= snprintf(tp->display, l, ", %u bytes", (int)total); + } + if (rate) + snprintf(tp->display + sizeof(tp->display) - l, l, + " | %u KiB/s", rate); +} + +void display_throughput(struct progress *progress, off_t total) +{ + struct throughput *tp; + struct timeval tv; + unsigned int misecs; + + if (!progress) + return; + tp = progress->throughput; + + gettimeofday(&tv, NULL); + + if (!tp) { + progress->throughput = tp = calloc(1, sizeof(*tp)); + if (tp) { + tp->prev_total = tp->curr_total = total; + tp->prev_tv = tv; + } + return; + } + tp->curr_total = total; + + /* + * We have x = bytes and y = microsecs. We want z = KiB/s: + * + * z = (x / 1024) / (y / 1000000) + * z = x / y * 1000000 / 1024 + * z = x / (y * 1024 / 1000000) + * z = x / y' + * + * To simplify things we'll keep track of misecs, or 1024th of a sec + * obtained with: + * + * y' = y * 1024 / 1000000 + * y' = y / (1000000 / 1024) + * y' = y / 977 + */ + misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024; + misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977; + + if (misecs > 512) { + unsigned int count, rate; + + count = total - tp->prev_total; + tp->prev_total = total; + tp->prev_tv = tv; + tp->avg_bytes += count; + tp->avg_misecs += misecs; + rate = tp->avg_bytes / tp->avg_misecs; + tp->avg_bytes -= tp->last_bytes[tp->idx]; + tp->avg_misecs -= tp->last_misecs[tp->idx]; + tp->last_bytes[tp->idx] = count; + tp->last_misecs[tp->idx] = misecs; + tp->idx = (tp->idx + 1) % TP_IDX_MAX; + + throughput_string(tp, total, rate); + if (progress->last_value != -1 && progress_update) + display(progress, progress->last_value, NULL); + } +} + +int display_progress(struct progress *progress, unsigned n) +{ + return progress ? display(progress, n, NULL) : 0; } -void start_progress_delay(struct progress *progress, const char *title, - const char *prefix, unsigned total, - unsigned percent_treshold, unsigned delay) +struct progress *start_progress_delay(const char *title, unsigned total, + unsigned percent_treshold, unsigned delay) { - progress->prefix = prefix; + struct progress *progress = malloc(sizeof(*progress)); + if (!progress) { + /* unlikely, but here's a good fallback */ + fprintf(stderr, "%s...\n", title); + fflush(stderr); + return NULL; + } + progress->title = title; progress->total = total; + progress->last_value = -1; progress->last_percent = -1; progress->delayed_percent_treshold = percent_treshold; - progress->delayed_title = title; progress->delay = delay; - progress->need_lf = 0; + progress->throughput = NULL; set_progress_signal(); + return progress; } -void stop_progress(struct progress *progress) +struct progress *start_progress(const char *title, unsigned total) { + return start_progress_delay(title, total, 0, 0); +} + +void stop_progress(struct progress **p_progress) +{ + stop_progress_msg(p_progress, "done"); +} + +void stop_progress_msg(struct progress **p_progress, const char *msg) +{ + struct progress *progress = *p_progress; + if (!progress) + return; + *p_progress = NULL; + if (progress->last_value != -1) { + /* Force the last update */ + char buf[strlen(msg) + 5]; + struct throughput *tp = progress->throughput; + if (tp) { + unsigned int rate = !tp->avg_misecs ? 0 : + tp->avg_bytes / tp->avg_misecs; + throughput_string(tp, tp->curr_total, rate); + } + progress_update = 1; + sprintf(buf, ", %s.\n", msg); + display(progress, progress->last_value, buf); + } clear_progress_signal(); - if (progress->need_lf) - fputc('\n', stderr); + free(progress->throughput); + free(progress); } diff --git a/progress.h b/progress.h index a7c17ca7c4..611e4c4d42 100644 --- a/progress.h +++ b/progress.h @@ -1,22 +1,14 @@ #ifndef PROGRESS_H #define PROGRESS_H -struct progress { - const char *prefix; - unsigned total; - unsigned last_percent; - unsigned delay; - unsigned delayed_percent_treshold; - const char *delayed_title; - int need_lf; -}; +struct progress; +void display_throughput(struct progress *progress, off_t total); int display_progress(struct progress *progress, unsigned n); -void start_progress(struct progress *progress, const char *title, - const char *prefix, unsigned total); -void start_progress_delay(struct progress *progress, const char *title, - const char *prefix, unsigned total, - unsigned percent_treshold, unsigned delay); -void stop_progress(struct progress *progress); +struct progress *start_progress(const char *title, unsigned total); +struct progress *start_progress_delay(const char *title, unsigned total, + unsigned percent_treshold, unsigned delay); +void stop_progress(struct progress **progress); +void stop_progress_msg(struct progress **progress, const char *msg); #endif @@ -12,37 +12,31 @@ * a'b ==> a'\''b ==> 'a'\''b' * a!b ==> a'\!'b ==> 'a'\!'b' */ -#undef EMIT -#define EMIT(x) do { if (++len < n) *bp++ = (x); } while(0) - static inline int need_bs_quote(char c) { return (c == '\'' || c == '!'); } -static size_t sq_quote_buf(char *dst, size_t n, const char *src) +void sq_quote_buf(struct strbuf *dst, const char *src) { - char c; - char *bp = dst; - size_t len = 0; - - EMIT('\''); - while ((c = *src++)) { - if (need_bs_quote(c)) { - EMIT('\''); - EMIT('\\'); - EMIT(c); - EMIT('\''); - } else { - EMIT(c); + char *to_free = NULL; + + if (dst->buf == src) + to_free = strbuf_detach(dst, NULL); + + strbuf_addch(dst, '\''); + while (*src) { + size_t len = strcspn(src, "'!"); + strbuf_add(dst, src, len); + src += len; + while (need_bs_quote(*src)) { + strbuf_addstr(dst, "'\\"); + strbuf_addch(dst, *src++); + strbuf_addch(dst, '\''); } } - EMIT('\''); - - if ( n ) - *bp = 0; - - return len; + strbuf_addch(dst, '\''); + free(to_free); } void sq_quote_print(FILE *stream, const char *src) @@ -62,11 +56,10 @@ void sq_quote_print(FILE *stream, const char *src) fputc('\'', stream); } -char *sq_quote_argv(const char** argv, int count) +void sq_quote_argv(struct strbuf *dst, const char** argv, int count, + size_t maxlen) { - char *buf, *to; int i; - size_t len = 0; /* Count argv if needed. */ if (count < 0) { @@ -74,53 +67,14 @@ char *sq_quote_argv(const char** argv, int count) ; /* just counting */ } - /* Special case: no argv. */ - if (!count) - return xcalloc(1,1); - - /* Get destination buffer length. */ - for (i = 0; i < count; i++) - len += sq_quote_buf(NULL, 0, argv[i]) + 1; - - /* Alloc destination buffer. */ - to = buf = xmalloc(len + 1); - /* Copy into destination buffer. */ + strbuf_grow(dst, 32 * count); for (i = 0; i < count; ++i) { - *to++ = ' '; - to += sq_quote_buf(to, len, argv[i]); + strbuf_addch(dst, ' '); + sq_quote_buf(dst, argv[i]); + if (maxlen && dst->len > maxlen) + die("Too many or long arguments"); } - - return buf; -} - -/* - * Append a string to a string buffer, with or without shell quoting. - * Return true if the buffer overflowed. - */ -int add_to_string(char **ptrp, int *sizep, const char *str, int quote) -{ - char *p = *ptrp; - int size = *sizep; - int oc; - int err = 0; - - if (quote) - oc = sq_quote_buf(p, size, str); - else { - oc = strlen(str); - memcpy(p, str, (size <= oc) ? size - 1 : oc); - } - - if (size <= oc) { - err = 1; - oc = size - 1; - } - - *ptrp += oc; - **ptrp = '\0'; - *sizep -= oc; - return err; } char *sq_dequote(char *arg) @@ -157,186 +111,214 @@ char *sq_dequote(char *arg) } } +/* 1 means: quote as octal + * 0 means: quote as octal if (quote_path_fully) + * -1 means: never quote + * c: quote as "\\c" + */ +#define X8(x) x, x, x, x, x, x, x, x +#define X16(x) X8(x), X8(x) +static signed char const sq_lookup[256] = { + /* 0 1 2 3 4 5 6 7 */ + /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a', + /* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1, + /* 0x10 */ X16(1), + /* 0x20 */ -1, -1, '"', -1, -1, -1, -1, -1, + /* 0x28 */ X16(-1), X16(-1), X16(-1), + /* 0x58 */ -1, -1, -1, -1,'\\', -1, -1, -1, + /* 0x60 */ X16(-1), X8(-1), + /* 0x78 */ -1, -1, -1, -1, -1, -1, -1, 1, + /* 0x80 */ /* set to 0 */ +}; + +static inline int sq_must_quote(char c) +{ + return sq_lookup[(unsigned char)c] + quote_path_fully > 0; +} + +/* returns the longest prefix not needing a quote up to maxlen if positive. + This stops at the first \0 because it's marked as a character needing an + escape */ +static size_t next_quote_pos(const char *s, ssize_t maxlen) +{ + size_t len; + if (maxlen < 0) { + for (len = 0; !sq_must_quote(s[len]); len++); + } else { + for (len = 0; len < maxlen && !sq_must_quote(s[len]); len++); + } + return len; +} + /* * C-style name quoting. * - * Does one of three things: - * - * (1) if outbuf and outfp are both NULL, inspect the input name and - * counts the number of bytes that are needed to hold c_style - * quoted version of name, counting the double quotes around - * it but not terminating NUL, and returns it. However, if name - * does not need c_style quoting, it returns 0. + * (1) if sb and fp are both NULL, inspect the input name and counts the + * number of bytes that are needed to hold c_style quoted version of name, + * counting the double quotes around it but not terminating NUL, and + * returns it. + * However, if name does not need c_style quoting, it returns 0. * - * (2) if outbuf is not NULL, it must point at a buffer large enough - * to hold the c_style quoted version of name, enclosing double - * quotes, and terminating NUL. Fills outbuf with c_style quoted - * version of name enclosed in double-quote pair. Return value - * is undefined. - * - * (3) if outfp is not NULL, outputs c_style quoted version of name, - * but not enclosed in double-quote pair. Return value is undefined. + * (2) if sb or fp are not NULL, it emits the c_style quoted version + * of name, enclosed with double quotes if asked and needed only. + * Return value is the same as in (1). */ - -static int quote_c_style_counted(const char *name, int namelen, - char *outbuf, FILE *outfp, int no_dq) +static size_t quote_c_style_counted(const char *name, ssize_t maxlen, + struct strbuf *sb, FILE *fp, int no_dq) { #undef EMIT -#define EMIT(c) \ - (outbuf ? (*outbuf++ = (c)) : outfp ? fputc(c, outfp) : (count++)) +#define EMIT(c) \ + do { \ + if (sb) strbuf_addch(sb, (c)); \ + if (fp) fputc((c), fp); \ + count++; \ + } while (0) +#define EMITBUF(s, l) \ + do { \ + if (sb) strbuf_add(sb, (s), (l)); \ + if (fp) fwrite((s), (l), 1, fp); \ + count += (l); \ + } while (0) + + size_t len, count = 0; + const char *p = name; -#define EMITQ() EMIT('\\') - - const char *sp; - unsigned char ch; - int count = 0, needquote = 0; + for (;;) { + int ch; - if (!no_dq) - EMIT('"'); - for (sp = name; sp < name + namelen; sp++) { - ch = *sp; - if (!ch) + len = next_quote_pos(p, maxlen); + if (len == maxlen || !p[len]) break; - if ((ch < ' ') || (ch == '"') || (ch == '\\') || - (quote_path_fully && (ch >= 0177))) { - needquote = 1; - switch (ch) { - case '\a': EMITQ(); ch = 'a'; break; - case '\b': EMITQ(); ch = 'b'; break; - case '\f': EMITQ(); ch = 'f'; break; - case '\n': EMITQ(); ch = 'n'; break; - case '\r': EMITQ(); ch = 'r'; break; - case '\t': EMITQ(); ch = 't'; break; - case '\v': EMITQ(); ch = 'v'; break; - - case '\\': /* fallthru */ - case '"': EMITQ(); break; - default: - /* octal */ - EMITQ(); - EMIT(((ch >> 6) & 03) + '0'); - EMIT(((ch >> 3) & 07) + '0'); - ch = (ch & 07) + '0'; - break; - } + + if (!no_dq && p == name) + EMIT('"'); + + EMITBUF(p, len); + EMIT('\\'); + p += len; + ch = (unsigned char)*p++; + if (sq_lookup[ch] >= ' ') { + EMIT(sq_lookup[ch]); + } else { + EMIT(((ch >> 6) & 03) + '0'); + EMIT(((ch >> 3) & 07) + '0'); + EMIT(((ch >> 0) & 07) + '0'); } - EMIT(ch); } + + EMITBUF(p, len); + if (p == name) /* no ending quote needed */ + return 0; + if (!no_dq) EMIT('"'); - if (outbuf) - *outbuf = 0; + return count; +} + +size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq) +{ + return quote_c_style_counted(name, -1, sb, fp, nodq); +} - return needquote ? count : 0; +void write_name_quoted(const char *name, FILE *fp, int terminator) +{ + if (terminator) { + quote_c_style(name, NULL, fp, 0); + } else { + fputs(name, fp); + } + fputc(terminator, fp); } -int quote_c_style(const char *name, char *outbuf, FILE *outfp, int no_dq) +extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, + const char *name, FILE *fp, int terminator) { - int cnt = strlen(name); - return quote_c_style_counted(name, cnt, outbuf, outfp, no_dq); + int needquote = 0; + + if (terminator) { + needquote = next_quote_pos(pfx, pfxlen) < pfxlen + || name[next_quote_pos(name, -1)]; + } + if (needquote) { + fputc('"', fp); + quote_c_style_counted(pfx, pfxlen, NULL, fp, 1); + quote_c_style(name, NULL, fp, 1); + fputc('"', fp); + } else { + fwrite(pfx, pfxlen, 1, fp); + fputs(name, fp); + } + fputc(terminator, fp); } /* * C-style name unquoting. * - * Quoted should point at the opening double quote. Returns - * an allocated memory that holds unquoted name, which the caller - * should free when done. Updates endp pointer to point at - * one past the ending double quote if given. + * Quoted should point at the opening double quote. + * + Returns 0 if it was able to unquote the string properly, and appends the + * result in the strbuf `sb'. + * + Returns -1 in case of error, and doesn't touch the strbuf. Though note + * that this function will allocate memory in the strbuf, so calling + * strbuf_release is mandatory whichever result unquote_c_style returns. + * + * Updates endp pointer to point at one past the ending double quote if given. */ - -char *unquote_c_style(const char *quoted, const char **endp) +int unquote_c_style(struct strbuf *sb, const char *quoted, const char **endp) { - const char *sp; - char *name = NULL, *outp = NULL; - int count = 0, ch, ac; - -#undef EMIT -#define EMIT(c) (outp ? (*outp++ = (c)) : (count++)) + size_t oldlen = sb->len, len; + int ch, ac; if (*quoted++ != '"') - return NULL; + return -1; + + for (;;) { + len = strcspn(quoted, "\"\\"); + strbuf_add(sb, quoted, len); + quoted += len; + + switch (*quoted++) { + case '"': + if (endp) + *endp = quoted + 1; + return 0; + case '\\': + break; + default: + goto error; + } + + switch ((ch = *quoted++)) { + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; - while (1) { - /* first pass counts and allocates, second pass fills */ - for (sp = quoted; (ch = *sp++) != '"'; ) { - if (ch == '\\') { - switch (ch = *sp++) { - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - - case '\\': case '"': - break; /* verbatim */ - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - /* octal */ + case '\\': case '"': + break; /* verbatim */ + + /* octal values with first digit over 4 overflow */ + case '0': case '1': case '2': case '3': ac = ((ch - '0') << 6); - if ((ch = *sp++) < '0' || '7' < ch) - return NULL; + if ((ch = *quoted++) < '0' || '7' < ch) + goto error; ac |= ((ch - '0') << 3); - if ((ch = *sp++) < '0' || '7' < ch) - return NULL; + if ((ch = *quoted++) < '0' || '7' < ch) + goto error; ac |= (ch - '0'); ch = ac; break; default: - return NULL; /* malformed */ - } + goto error; } - EMIT(ch); + strbuf_addch(sb, ch); } - if (name) { - *outp = 0; - if (endp) - *endp = sp; - return name; - } - outp = name = xmalloc(count + 1); - } -} - -void write_name_quoted(const char *prefix, int prefix_len, - const char *name, int quote, FILE *out) -{ - int needquote; - - if (!quote) { - no_quote: - if (prefix_len) - fprintf(out, "%.*s", prefix_len, prefix); - fputs(name, out); - return; - } - - needquote = 0; - if (prefix_len) - needquote = quote_c_style_counted(prefix, prefix_len, - NULL, NULL, 0); - if (!needquote) - needquote = quote_c_style(name, NULL, NULL, 0); - if (needquote) { - fputc('"', out); - if (prefix_len) - quote_c_style_counted(prefix, prefix_len, - NULL, out, 1); - quote_c_style(name, NULL, out, 1); - fputc('"', out); - } - else - goto no_quote; + error: + strbuf_setlen(sb, oldlen); + return -1; } /* quoting as a string literal for other languages */ @@ -29,13 +29,10 @@ */ extern void sq_quote_print(FILE *stream, const char *src); -extern char *sq_quote_argv(const char** argv, int count); -/* - * Append a string to a string buffer, with or without shell quoting. - * Return true if the buffer overflowed. - */ -extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote); +extern void sq_quote_buf(struct strbuf *, const char *src); +extern void sq_quote_argv(struct strbuf *, const char **argv, int count, + size_t maxlen); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have @@ -43,12 +40,12 @@ extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote); */ extern char *sq_dequote(char *); -extern int quote_c_style(const char *name, char *outbuf, FILE *outfp, - int nodq); -extern char *unquote_c_style(const char *quoted, const char **endp); +extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); +extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); -extern void write_name_quoted(const char *prefix, int prefix_len, - const char *name, int quote, FILE *out); +extern void write_name_quoted(const char *name, FILE *, int terminator); +extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, + const char *name, FILE *, int terminator); /* quoting as a string literal for other languages */ extern void perl_quote_print(FILE *stream, const char *src); diff --git a/read-cache.c b/read-cache.c index c3dbf89426..7db55883d6 100644 --- a/read-cache.c +++ b/read-cache.c @@ -350,6 +350,7 @@ int remove_file_from_index(struct index_state *istate, const char *path) int pos = index_name_pos(istate, path, strlen(path)); if (pos < 0) pos = -pos-1; + cache_tree_invalidate_path(istate->cache_tree, path); while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path)) remove_index_entry_at(istate, pos); return 0; @@ -435,7 +436,6 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) die("unable to add %s to index",path); if (verbose) printf("add '%s'\n", path); - cache_tree_invalidate_path(istate->cache_tree, path); return 0; } @@ -703,6 +703,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE; int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; + cache_tree_invalidate_path(istate->cache_tree, ce->name); pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags)); /* existing match? Just replace it. */ @@ -1151,7 +1152,7 @@ int write_index(struct index_state *istate, int newfd) { SHA_CTX c; struct cache_header hdr; - int i, removed; + int i, err, removed; struct cache_entry **cache = istate->cache; int entries = istate->cache_nr; @@ -1180,16 +1181,15 @@ int write_index(struct index_state *istate, int newfd) /* Write extension data here */ if (istate->cache_tree) { - unsigned long sz; - void *data = cache_tree_write(istate->cache_tree, &sz); - if (data && - !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) && - !ce_write(&c, newfd, data, sz)) - free(data); - else { - free(data); + struct strbuf sb; + + strbuf_init(&sb, 0); + cache_tree_write(&sb, istate->cache_tree); + err = write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) return -1; - } } return ce_flush(&c, newfd); } diff --git a/receive-pack.c b/receive-pack.c index 1521d0b2de..ed44b897f6 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -204,8 +204,6 @@ static const char *update(struct command *cmd) error("failed to delete %s", name); return "failed to delete"; } - fprintf(stderr, "%s: %s -> deleted\n", name, - sha1_to_hex(old_sha1)); return NULL; /* good */ } else { @@ -217,8 +215,6 @@ static const char *update(struct command *cmd) if (write_ref_sha1(lock, new_sha1, "push")) { return "failed to write"; /* error() already called */ } - fprintf(stderr, "%s: %s -> %s\n", name, - sha1_to_hex(old_sha1), sha1_to_hex(new_sha1)); return NULL; /* good */ } } @@ -382,9 +378,8 @@ static const char *unpack(void) } } else { const char *keeper[6]; - int s, len, status; + int s, status; char keep_arg[256]; - char packname[46]; struct child_process ip; s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid()); @@ -403,26 +398,7 @@ static const char *unpack(void) ip.git_cmd = 1; if (start_command(&ip)) return "index-pack fork failed"; - - /* - * The first thing we expects from index-pack's output - * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where - * %40s is the newly created pack SHA1 name. In the "keep" - * case, we need it to remove the corresponding .keep file - * later on. If we don't get that then tough luck with it. - */ - for (len = 0; - len < 46 && (s = xread(ip.out, packname+len, 46-len)) > 0; - len += s); - if (len == 46 && packname[45] == '\n' && - memcmp(packname, "keep\t", 5) == 0) { - char path[PATH_MAX]; - packname[45] = 0; - snprintf(path, sizeof(path), "%s/pack/pack-%s.keep", - get_object_directory(), packname + 5); - pack_lockfile = xstrdup(path); - } - + pack_lockfile = index_pack_lockfile(ip.out); status = finish_command(&ip); if (!status) { reprepare_packed_git(); @@ -2,6 +2,7 @@ #include "refs.h" #include "object.h" #include "tag.h" +#include "dir.h" /* ISSYMREF=01 and ISPACKED=02 are public interfaces */ #define REF_KNOWS_PEELED 04 @@ -579,18 +580,6 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data) return do_for_each_ref("refs/remotes/", fn, 13, cb_data); } -/* NEEDSWORK: This is only used by ssh-upload and it should go; the - * caller should do resolve_ref or read_ref like everybody else. Or - * maybe everybody else should use get_ref_sha1() instead of doing - * read_ref(). - */ -int get_ref_sha1(const char *ref, unsigned char *sha1) -{ - if (check_ref_format(ref)) - return -1; - return read_ref(mkpath("refs/%s", ref), sha1); -} - /* * Make sure "ref" is something reasonable to have under ".git/refs/"; * We do not like it if: @@ -671,57 +660,23 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static int remove_empty_dir_recursive(char *path, int len) -{ - DIR *dir = opendir(path); - struct dirent *e; - int ret = 0; - - if (!dir) - return -1; - if (path[len-1] != '/') - path[len++] = '/'; - while ((e = readdir(dir)) != NULL) { - struct stat st; - int namlen; - if ((e->d_name[0] == '.') && - ((e->d_name[1] == 0) || - ((e->d_name[1] == '.') && e->d_name[2] == 0))) - continue; /* "." and ".." */ - - namlen = strlen(e->d_name); - if ((len + namlen < PATH_MAX) && - strcpy(path + len, e->d_name) && - !lstat(path, &st) && - S_ISDIR(st.st_mode) && - !remove_empty_dir_recursive(path, len + namlen)) - continue; /* happy */ - - /* path too long, stat fails, or non-directory still exists */ - ret = -1; - break; - } - closedir(dir); - if (!ret) { - path[len] = 0; - ret = rmdir(path); - } - return ret; -} - -static int remove_empty_directories(char *file) +static int remove_empty_directories(const char *file) { /* we want to create a file but there is a directory there; * if that is an empty directory (or a directory that contains * only empty directories), remove them. */ - char path[PATH_MAX]; - int len = strlen(file); + struct strbuf path; + int result; - if (len >= PATH_MAX) /* path too long ;-) */ - return -1; - strcpy(path, file); - return remove_empty_dir_recursive(path, len); + strbuf_init(&path, 20); + strbuf_addstr(&path, file); + + result = remove_dir_recursively(&path, 1); + + strbuf_release(&path); + + return result; } static int is_refname_available(const char *ref, const char *oldref, @@ -1246,15 +1201,11 @@ int create_symref(const char *ref_target, const char *refs_heads_master, static char *ref_msg(const char *line, const char *endp) { const char *ep; - char *msg; - line += 82; - for (ep = line; ep < endp && *ep != '\n'; ep++) - ; - msg = xmalloc(ep - line + 1); - memcpy(msg, line, ep - line); - msg[ep - line] = 0; - return msg; + ep = memchr(line, '\n', endp - line); + if (!ep) + ep = endp; + return xmemdupz(line, ep - line); } int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) @@ -1455,3 +1406,38 @@ int for_each_reflog(each_ref_fn fn, void *cb_data) { return do_for_each_reflog("", fn, cb_data); } + +int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr) +{ + static struct ref_lock *lock; + lock = lock_any_ref_for_update(refname, oldval, flags); + if (!lock) { + const char *str = "Cannot lock the ref '%s'."; + switch (onerr) { + case MSG_ON_ERR: error(str, refname); break; + case DIE_ON_ERR: die(str, refname); break; + case QUIET_ON_ERR: break; + } + return 1; + } + if (write_ref_sha1(lock, sha1, action) < 0) { + const char *str = "Cannot update the ref '%s'."; + switch (onerr) { + case MSG_ON_ERR: error(str, refname); break; + case DIE_ON_ERR: die(str, refname); break; + case QUIET_ON_ERR: break; + } + return 1; + } + return 0; +} + +struct ref *find_ref_by_name(struct ref *list, const char *name) +{ + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return list; + return NULL; +} @@ -26,9 +26,6 @@ extern int for_each_remote_ref(each_ref_fn, void *); extern int peel_ref(const char *, unsigned char *); -/** Reads the refs file specified into sha1 **/ -extern int get_ref_sha1(const char *ref, unsigned char *sha1); - /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); @@ -64,4 +61,10 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg /** resolve ref in nested "gitlink" repository */ extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result); +/** lock a ref and then write its file */ +enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR }; +int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr); + #endif /* REFS_H */ @@ -5,6 +5,12 @@ static struct remote **remotes; static int allocated_remotes; +static struct branch **branches; +static int allocated_branches; + +static struct branch *current_branch; +static const char *default_remote_name; + #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; @@ -26,13 +32,13 @@ static void add_fetch_refspec(struct remote *remote, const char *ref) remote->fetch_refspec_nr = nr; } -static void add_uri(struct remote *remote, const char *uri) +static void add_url(struct remote *remote, const char *url) { - int nr = remote->uri_nr + 1; - remote->uri = - xrealloc(remote->uri, nr * sizeof(char *)); - remote->uri[nr-1] = uri; - remote->uri_nr = nr; + int nr = remote->url_nr + 1; + remote->url = + xrealloc(remote->url, nr * sizeof(char *)); + remote->url[nr-1] = url; + remote->url_nr = nr; } static struct remote *make_remote(const char *name, int len) @@ -67,6 +73,54 @@ static struct remote *make_remote(const char *name, int len) return remotes[empty]; } +static void add_merge(struct branch *branch, const char *name) +{ + int nr = branch->merge_nr + 1; + branch->merge_name = + xrealloc(branch->merge_name, nr * sizeof(char *)); + branch->merge_name[nr-1] = name; + branch->merge_nr = nr; +} + +static struct branch *make_branch(const char *name, int len) +{ + int i, empty = -1; + char *refname; + + for (i = 0; i < allocated_branches; i++) { + if (!branches[i]) { + if (empty < 0) + empty = i; + } else { + if (len ? (!strncmp(name, branches[i]->name, len) && + !branches[i]->name[len]) : + !strcmp(name, branches[i]->name)) + return branches[i]; + } + } + + if (empty < 0) { + empty = allocated_branches; + allocated_branches += allocated_branches ? allocated_branches : 1; + branches = xrealloc(branches, + sizeof(*branches) * allocated_branches); + memset(branches + empty, 0, + (allocated_branches - empty) * sizeof(*branches)); + } + branches[empty] = xcalloc(1, sizeof(struct branch)); + if (len) + branches[empty]->name = xstrndup(name, len); + else + branches[empty]->name = xstrdup(name); + refname = malloc(strlen(name) + strlen("refs/heads/") + 1); + strcpy(refname, "refs/heads/"); + strcpy(refname + strlen("refs/heads/"), + branches[empty]->name); + branches[empty]->refname = refname; + + return branches[empty]; +} + static void read_remotes_file(struct remote *remote) { FILE *f = fopen(git_path("remotes/%s", remote->name), "r"); @@ -100,7 +154,7 @@ static void read_remotes_file(struct remote *remote) switch (value_list) { case 0: - add_uri(remote, xstrdup(s)); + add_url(remote, xstrdup(s)); break; case 1: add_push_refspec(remote, xstrdup(s)); @@ -116,6 +170,8 @@ static void read_remotes_file(struct remote *remote) static void read_branches_file(struct remote *remote) { const char *slash = strchr(remote->name, '/'); + char *frag; + char *branch; int n = slash ? slash - remote->name : 1000; FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); char *s, *p; @@ -141,23 +197,41 @@ static void read_branches_file(struct remote *remote) strcpy(p, s); if (slash) strcat(p, slash); - add_uri(remote, p); + frag = strchr(p, '#'); + if (frag) { + *(frag++) = '\0'; + branch = xmalloc(strlen(frag) + 12); + strcpy(branch, "refs/heads/"); + strcat(branch, frag); + } else { + branch = "refs/heads/master"; + } + add_url(remote, p); + add_fetch_refspec(remote, branch); + remote->fetch_tags = 1; /* always auto-follow */ } -static char *default_remote_name = NULL; -static const char *current_branch = NULL; -static int current_branch_len = 0; - static int handle_config(const char *key, const char *value) { const char *name; const char *subkey; struct remote *remote; - if (!prefixcmp(key, "branch.") && current_branch && - !strncmp(key + 7, current_branch, current_branch_len) && - !strcmp(key + 7 + current_branch_len, ".remote")) { - free(default_remote_name); - default_remote_name = xstrdup(value); + struct branch *branch; + if (!prefixcmp(key, "branch.")) { + name = key + 7; + subkey = strrchr(name, '.'); + branch = make_branch(name, subkey - name); + if (!subkey) + return 0; + if (!value) + return 0; + if (!strcmp(subkey, ".remote")) { + branch->remote_name = xstrdup(value); + if (branch == current_branch) + default_remote_name = branch->remote_name; + } else if (!strcmp(subkey, ".merge")) + add_merge(branch, xstrdup(value)); + return 0; } if (prefixcmp(key, "remote.")) return 0; @@ -186,7 +260,7 @@ static int handle_config(const char *key, const char *value) return 0; /* ignore unknown booleans */ } if (!strcmp(subkey, ".url")) { - add_uri(remote, xstrdup(value)); + add_url(remote, xstrdup(value)); } else if (!strcmp(subkey, ".push")) { add_push_refspec(remote, xstrdup(value)); } else if (!strcmp(subkey, ".fetch")) { @@ -196,6 +270,14 @@ static int handle_config(const char *key, const char *value) remote->receivepack = xstrdup(value); else error("more than one receivepack given, using the first"); + } else if (!strcmp(subkey, ".uploadpack")) { + if (!remote->uploadpack) + remote->uploadpack = xstrdup(value); + else + error("more than one uploadpack given, using the first"); + } else if (!strcmp(subkey, ".tagopt")) { + if (!strcmp(value, "--no-tags")) + remote->fetch_tags = -1; } return 0; } @@ -212,13 +294,13 @@ static void read_config(void) head_ref = resolve_ref("HEAD", sha1, 0, &flag); if (head_ref && (flag & REF_ISSYMREF) && !prefixcmp(head_ref, "refs/heads/")) { - current_branch = head_ref + strlen("refs/heads/"); - current_branch_len = strlen(current_branch); + current_branch = + make_branch(head_ref + strlen("refs/heads/"), 0); } git_config(handle_config); } -static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) +struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) { int i; struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec); @@ -265,14 +347,14 @@ struct remote *remote_get(const char *name) name = default_remote_name; ret = make_remote(name, 0); if (name[0] != '/') { - if (!ret->uri) + if (!ret->url) read_remotes_file(ret); - if (!ret->uri) + if (!ret->url) read_branches_file(ret); } - if (!ret->uri) - add_uri(ret, name); - if (!ret->uri) + if (!ret->url) + add_url(ret, name); + if (!ret->url) return NULL; ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec); ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec); @@ -298,16 +380,62 @@ int for_each_remote(each_remote_fn fn, void *priv) return result; } -int remote_has_uri(struct remote *remote, const char *uri) +void ref_remove_duplicates(struct ref *ref_map) +{ + struct ref **posn; + struct ref *next; + for (; ref_map; ref_map = ref_map->next) { + if (!ref_map->peer_ref) + continue; + posn = &ref_map->next; + while (*posn) { + if ((*posn)->peer_ref && + !strcmp((*posn)->peer_ref->name, + ref_map->peer_ref->name)) { + if (strcmp((*posn)->name, ref_map->name)) + die("%s tracks both %s and %s", + ref_map->peer_ref->name, + (*posn)->name, ref_map->name); + next = (*posn)->next; + free((*posn)->peer_ref); + free(*posn); + *posn = next; + } else { + posn = &(*posn)->next; + } + } + } +} + +int remote_has_url(struct remote *remote, const char *url) { int i; - for (i = 0; i < remote->uri_nr; i++) { - if (!strcmp(remote->uri[i], uri)) + for (i = 0; i < remote->url_nr; i++) { + if (!strcmp(remote->url[i], url)) return 1; } return 0; } +/* + * Returns true if, under the matching rules for fetching, name is the + * same as the given full name. + */ +static int ref_matches_abbrev(const char *name, const char *full) +{ + if (!prefixcmp(name, "refs/") || !strcmp(name, "HEAD")) + return !strcmp(name, full); + if (prefixcmp(full, "refs/")) + return 0; + if (!prefixcmp(name, "heads/") || + !prefixcmp(name, "tags/") || + !prefixcmp(name, "remotes/")) + return !strcmp(name, full + 5); + if (prefixcmp(full + 5, "heads/")) + return 0; + return !strcmp(full + 11, name); +} + int remote_find_tracking(struct remote *remote, struct refspec *refspec) { int find_src = refspec->src == NULL; @@ -315,7 +443,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec) int i; if (find_src) { - if (refspec->dst == NULL) + if (!refspec->dst) return error("find_tracking: need either src or dst"); needle = refspec->dst; result = &refspec->src; @@ -357,6 +485,26 @@ struct ref *alloc_ref(unsigned namelen) return ret; } +static struct ref *copy_ref(const struct ref *ref) +{ + struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1); + memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1); + ret->next = NULL; + return ret; +} + +struct ref *copy_ref_list(const struct ref *ref) +{ + struct ref *ret = NULL; + struct ref **tail = &ret; + while (ref) { + *tail = copy_ref(ref); + ref = ref->next; + tail = &((*tail)->next); + } + return ret; +} + void free_refs(struct ref *ref) { struct ref *next; @@ -489,15 +637,12 @@ static int match_explicit(struct ref *src, struct ref *dst, * way to delete 'other' ref at the remote end. */ matched_src = try_explicit_object_name(rs->src); - if (matched_src) - break; - error("src refspec %s does not match any.", - rs->src); + if (!matched_src) + error("src refspec %s does not match any.", rs->src); break; default: matched_src = NULL; - error("src refspec %s matches more than one.", - rs->src); + error("src refspec %s matches more than one.", rs->src); break; } @@ -527,7 +672,7 @@ static int match_explicit(struct ref *src, struct ref *dst, dst_value); break; } - if (errs || matched_dst == NULL) + if (errs || !matched_dst) return 1; if (matched_dst->peer_ref) { errs = 1; @@ -551,14 +696,6 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, return -errs; } -static struct ref *find_ref_by_name(struct ref *list, const char *name) -{ - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return list; - return NULL; -} - static const struct refspec *check_pattern_match(const struct refspec *rs, int rs_nr, const struct ref *src) @@ -577,10 +714,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, * without thinking. */ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all) + int nr_refspec, const char **refspec, int flags) { struct refspec *rs = parse_ref_spec(nr_refspec, (const char **) refspec); + int send_all = flags & MATCH_REFS_ALL; + int send_mirror = flags & MATCH_REFS_MIRROR; if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) return -1; @@ -597,7 +736,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (!pat) continue; } - else if (prefixcmp(src->name, "refs/heads/")) + else if (!send_mirror && prefixcmp(src->name, "refs/heads/")) /* * "matching refs"; traditionally we pushed everything * including refs outside refs/heads/ hierarchy, but @@ -618,10 +757,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (dst_peer && dst_peer->peer_ref) /* We're already sending something to this ref. */ goto free_name; - if (!dst_peer && !nr_refspec && !all) - /* Remote doesn't have it, and we have no + + if (!dst_peer && !nr_refspec && !(send_all || send_mirror)) + /* + * Remote doesn't have it, and we have no * explicit pattern, and we don't have - * --all. */ + * --all nor --mirror. + */ goto free_name; if (!dst_peer) { /* Create a new one and link it */ @@ -636,3 +778,154 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, } return 0; } + +struct branch *branch_get(const char *name) +{ + struct branch *ret; + + read_config(); + if (!name || !*name || !strcmp(name, "HEAD")) + ret = current_branch; + else + ret = make_branch(name, 0); + if (ret && ret->remote_name) { + ret->remote = remote_get(ret->remote_name); + if (ret->merge_nr) { + int i; + ret->merge = xcalloc(sizeof(*ret->merge), + ret->merge_nr); + for (i = 0; i < ret->merge_nr; i++) { + ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); + ret->merge[i]->src = xstrdup(ret->merge_name[i]); + remote_find_tracking(ret->remote, + ret->merge[i]); + } + } + } + return ret; +} + +int branch_has_merge_config(struct branch *branch) +{ + return branch && !!branch->merge; +} + +int branch_merge_matches(struct branch *branch, + int i, + const char *refname) +{ + if (!branch || i < 0 || i >= branch->merge_nr) + return 0; + return ref_matches_abbrev(branch->merge[i]->src, refname); +} + +static struct ref *get_expanded_map(const struct ref *remote_refs, + const struct refspec *refspec) +{ + const struct ref *ref; + struct ref *ret = NULL; + struct ref **tail = &ret; + + int remote_prefix_len = strlen(refspec->src); + int local_prefix_len = strlen(refspec->dst); + + for (ref = remote_refs; ref; ref = ref->next) { + if (strchr(ref->name, '^')) + continue; /* a dereference item */ + if (!prefixcmp(ref->name, refspec->src)) { + const char *match; + struct ref *cpy = copy_ref(ref); + match = ref->name + remote_prefix_len; + + cpy->peer_ref = alloc_ref(local_prefix_len + + strlen(match) + 1); + sprintf(cpy->peer_ref->name, "%s%s", + refspec->dst, match); + if (refspec->force) + cpy->peer_ref->force = 1; + *tail = cpy; + tail = &cpy->next; + } + } + + return ret; +} + +static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name) +{ + const struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + if (ref_matches_abbrev(name, ref->name)) + return ref; + } + return NULL; +} + +struct ref *get_remote_ref(const struct ref *remote_refs, const char *name) +{ + const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name); + + if (!ref) + return NULL; + + return copy_ref(ref); +} + +static struct ref *get_local_ref(const char *name) +{ + struct ref *ret; + if (!name) + return NULL; + + if (!prefixcmp(name, "refs/")) { + ret = alloc_ref(strlen(name) + 1); + strcpy(ret->name, name); + return ret; + } + + if (!prefixcmp(name, "heads/") || + !prefixcmp(name, "tags/") || + !prefixcmp(name, "remotes/")) { + ret = alloc_ref(strlen(name) + 6); + sprintf(ret->name, "refs/%s", name); + return ret; + } + + ret = alloc_ref(strlen(name) + 12); + sprintf(ret->name, "refs/heads/%s", name); + return ret; +} + +int get_fetch_map(const struct ref *remote_refs, + const struct refspec *refspec, + struct ref ***tail, + int missing_ok) +{ + struct ref *ref_map, *rm; + + if (refspec->pattern) { + ref_map = get_expanded_map(remote_refs, refspec); + } else { + const char *name = refspec->src[0] ? refspec->src : "HEAD"; + + ref_map = get_remote_ref(remote_refs, name); + if (!missing_ok && !ref_map) + die("Couldn't find remote ref %s", name); + if (ref_map) { + ref_map->peer_ref = get_local_ref(refspec->dst); + if (ref_map->peer_ref && refspec->force) + ref_map->peer_ref->force = 1; + } + } + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref && check_ref_format(rm->peer_ref->name + 5)) + die("* refusing to create funny ref '%s' locally", + rm->peer_ref->name); + } + + if (ref_map) + tail_link_ref(ref_map, tail); + + return 0; +} @@ -4,8 +4,8 @@ struct remote { const char *name; - const char **uri; - int uri_nr; + const char **url; + int url_nr; const char **push_refspec; struct refspec *push; @@ -15,7 +15,16 @@ struct remote { struct refspec *fetch; int fetch_refspec_nr; + /* + * -1 to never fetch tags + * 0 to auto-follow tags on heuristic (default) + * 1 to always auto-follow tags + * 2 to always fetch tags + */ + int fetch_tags; + const char *receivepack; + const char *uploadpack; }; struct remote *remote_get(const char *name); @@ -23,7 +32,7 @@ struct remote *remote_get(const char *name); typedef int each_remote_fn(struct remote *remote, void *priv); int for_each_remote(each_remote_fn fn, void *priv); -int remote_has_uri(struct remote *remote, const char *uri); +int remote_has_url(struct remote *remote, const char *url); struct refspec { unsigned force : 1; @@ -35,17 +44,69 @@ struct refspec { struct ref *alloc_ref(unsigned namelen); +struct ref *copy_ref_list(const struct ref *ref); + +int check_ref_type(const struct ref *ref, int flags); + /* * Frees the entire list and peers of elements. */ void free_refs(struct ref *ref); +/* + * Removes and frees any duplicate refs in the map. + */ +void ref_remove_duplicates(struct ref *ref_map); + +struct refspec *parse_ref_spec(int nr_refspec, const char **refspec); + int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all); + int nr_refspec, const char **refspec, int all); + +/* + * Given a list of the remote refs and the specification of things to + * fetch, makes a (separate) list of the refs to fetch and the local + * refs to store into. + * + * *tail is the pointer to the tail pointer of the list of results + * beforehand, and will be set to the tail pointer of the list of + * results afterward. + * + * missing_ok is usually false, but when we are adding branch.$name.merge + * it is Ok if the branch is not at the remote anymore. + */ +int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec, + struct ref ***tail, int missing_ok); + +struct ref *get_remote_ref(const struct ref *remote_refs, const char *name); /* * For the given remote, reads the refspec's src and sets the other fields. */ int remote_find_tracking(struct remote *remote, struct refspec *refspec); +struct branch { + const char *name; + const char *refname; + + const char *remote_name; + struct remote *remote; + + const char **merge_name; + struct refspec **merge; + int merge_nr; +}; + +struct branch *branch_get(const char *name); + +int branch_has_merge_config(struct branch *branch); +int branch_merge_matches(struct branch *, int n, const char *); + +/* Flags to match_refs. */ +enum match_refs_flags { + MATCH_REFS_NONE = 0, + MATCH_REFS_ALL = (1 << 0), + MATCH_REFS_MIRROR = (1 << 1), +}; + #endif diff --git a/revision.c b/revision.c index 0ba0729b08..2a59035192 100644 --- a/revision.c +++ b/revision.c @@ -10,6 +10,8 @@ #include "reflog-walk.h" #include "patch-ids.h" +volatile show_early_output_fn_t show_early_output; + static char *path_name(struct name_path *path, const char *name) { struct name_path *p; @@ -257,7 +259,7 @@ static void file_add_remove(struct diff_options *options, } tree_difference = diff; if (tree_difference == REV_TREE_DIFFERENT) - options->has_changes = 1; + DIFF_OPT_SET(options, HAS_CHANGES); } static void file_change(struct diff_options *options, @@ -267,7 +269,7 @@ static void file_change(struct diff_options *options, const char *base, const char *path) { tree_difference = REV_TREE_DIFFERENT; - options->has_changes = 1; + DIFF_OPT_SET(options, HAS_CHANGES); } static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2) @@ -277,7 +279,7 @@ static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree if (!t2) return REV_TREE_DIFFERENT; tree_difference = REV_TREE_SAME; - revs->pruning.has_changes = 0; + DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &revs->pruning) < 0) return REV_TREE_DIFFERENT; @@ -301,7 +303,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) init_tree_desc(&empty, "", 0); tree_difference = REV_TREE_SAME; - revs->pruning.has_changes = 0; + DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); retval = diff_tree(&empty, &real, "", &revs->pruning); free(tree); @@ -313,15 +315,28 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) struct commit_list **pp, *parent; int tree_changed = 0, tree_same = 0; + /* + * If we don't do pruning, everything is interesting + */ + if (!revs->prune) + return; + if (!commit->tree) return; if (!commit->parents) { - if (!rev_same_tree_as_empty(revs, commit->tree)) - commit->object.flags |= TREECHANGE; + if (rev_same_tree_as_empty(revs, commit->tree)) + commit->object.flags |= TREESAME; return; } + /* + * Normal non-merge commit? If we don't want to make the + * history dense, we consider it always to be a change.. + */ + if (!revs->dense && !commit->parents->next) + return; + pp = &commit->parents; while ((parent = *pp) != NULL) { struct commit *p = parent->item; @@ -345,6 +360,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) } parent->next = NULL; commit->parents = parent; + commit->object.flags |= TREESAME; return; case REV_TREE_NEW: @@ -373,7 +389,8 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); } if (tree_changed && !tree_same) - commit->object.flags |= TREECHANGE; + return; + commit->object.flags |= TREESAME; } static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list) @@ -420,8 +437,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str * simplify the commit history and find the parent * that has no differences in the path set if one exists. */ - if (revs->prune_fn) - revs->prune_fn(revs, commit); + try_to_simplify_commit(revs, commit); if (revs->no_walk) return 0; @@ -540,6 +556,7 @@ static int limit_list(struct rev_info *revs) struct commit_list *entry = list; struct commit *commit = list->item; struct object *obj = &commit->object; + show_early_output_fn_t show; list = list->next; free(entry); @@ -557,6 +574,13 @@ static int limit_list(struct rev_info *revs) if (revs->min_age != -1 && (commit->date > revs->min_age)) continue; p = &commit_list_insert(commit, p)->next; + + show = show_early_output; + if (!show) + continue; + + show(revs, newlist); + show_early_output = NULL; } if (revs->cherry_pick) cherry_pick_list(newlist, revs); @@ -669,8 +693,8 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->abbrev = DEFAULT_ABBREV; revs->ignore_merges = 1; revs->simplify_history = 1; - revs->pruning.recursive = 1; - revs->pruning.quiet = 1; + DIFF_OPT_SET(&revs->pruning, RECURSIVE); + DIFF_OPT_SET(&revs->pruning, QUIET); revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; revs->lifo = 1; @@ -681,12 +705,6 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->skip_count = -1; revs->max_count = -1; - revs->prune_fn = NULL; - revs->prune_data = NULL; - - revs->topo_setter = topo_sort_default_setter; - revs->topo_getter = topo_sort_default_getter; - revs->commit_format = CMIT_FMT_DEFAULT; diff_setup(&revs->diffopt); @@ -1001,6 +1019,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->topo_order = 1; continue; } + if (!prefixcmp(arg, "--early-output")) { + int count = 100; + switch (arg[14]) { + case '=': + count = atoi(arg+15); + /* Fallthrough */ + case 0: + revs->topo_order = 1; + revs->early_output = count; + continue; + } + } if (!strcmp(arg, "--parents")) { revs->parents = 1; continue; @@ -1061,13 +1091,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (!strcmp(arg, "-r")) { revs->diff = 1; - revs->diffopt.recursive = 1; + DIFF_OPT_SET(&revs->diffopt, RECURSIVE); continue; } if (!strcmp(arg, "-t")) { revs->diff = 1; - revs->diffopt.recursive = 1; - revs->diffopt.tree_in_recursive = 1; + DIFF_OPT_SET(&revs->diffopt, RECURSIVE); + DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE); continue; } if (!strcmp(arg, "-m")) { @@ -1141,22 +1171,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch continue; } if (!strncmp(arg, "--date=", 7)) { - if (!strcmp(arg + 7, "relative")) - revs->date_mode = DATE_RELATIVE; - else if (!strcmp(arg + 7, "iso8601") || - !strcmp(arg + 7, "iso")) - revs->date_mode = DATE_ISO8601; - else if (!strcmp(arg + 7, "rfc2822") || - !strcmp(arg + 7, "rfc")) - revs->date_mode = DATE_RFC2822; - else if (!strcmp(arg + 7, "short")) - revs->date_mode = DATE_SHORT; - else if (!strcmp(arg + 7, "local")) - revs->date_mode = DATE_LOCAL; - else if (!strcmp(arg + 7, "default")) - revs->date_mode = DATE_NORMAL; - else - die("unknown date format %s", arg); + revs->date_mode = parse_date_format(arg + 7); continue; } if (!strcmp(arg, "--log-size")) { @@ -1264,7 +1279,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->diff = 1; /* Pickaxe and rename following needs diffs */ - if (revs->diffopt.pickaxe || revs->diffopt.follow_renames) + if (revs->diffopt.pickaxe || DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) revs->diff = 1; if (revs->topo_order) @@ -1273,8 +1288,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (revs->prune_data) { diff_tree_setup_paths(revs->prune_data, &revs->pruning); /* Can't prune commits with rename following: the paths change.. */ - if (!revs->diffopt.follow_renames) - revs->prune_fn = try_to_simplify_commit; + if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) + revs->prune = 1; if (!revs->full_diff) diff_tree_setup_paths(revs->prune_data, &revs->diffopt); } @@ -1325,9 +1340,7 @@ int prepare_revision_walk(struct rev_info *revs) if (limit_list(revs) < 0) return -1; if (revs->topo_order) - sort_in_topological_order_fn(&revs->commits, revs->lifo, - revs->topo_setter, - revs->topo_getter); + sort_in_topological_order(&revs->commits, revs->lifo); return 0; } @@ -1346,7 +1359,9 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp return rewrite_one_error; if (p->parents && p->parents->next) return rewrite_one_ok; - if (p->object.flags & (TREECHANGE | UNINTERESTING)) + if (p->object.flags & UNINTERESTING) + return rewrite_one_ok; + if (!(p->object.flags & TREESAME)) return rewrite_one_ok; if (!p->parents) return rewrite_one_noparents; @@ -1403,6 +1418,36 @@ static int commit_match(struct commit *commit, struct rev_info *opt) commit->buffer, strlen(commit->buffer)); } +enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) +{ + if (commit->object.flags & SHOWN) + return commit_ignore; + if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed)) + return commit_ignore; + if (commit->object.flags & UNINTERESTING) + return commit_ignore; + if (revs->min_age != -1 && (commit->date > revs->min_age)) + return commit_ignore; + if (revs->no_merges && commit->parents && commit->parents->next) + return commit_ignore; + if (!commit_match(commit, revs)) + return commit_ignore; + if (revs->prune && revs->dense) { + /* Commit without changes? */ + if (commit->object.flags & TREESAME) { + /* drop merges unless we want parenthood */ + if (!revs->parents) + return commit_ignore; + /* non-merge - always ignore it */ + if (!commit->parents || !commit->parents->next) + return commit_ignore; + } + if (revs->parents && rewrite_parents(revs, commit) < 0) + return commit_error; + } + return commit_show; +} + static struct commit *get_revision_1(struct rev_info *revs) { if (!revs->commits) @@ -1430,36 +1475,15 @@ static struct commit *get_revision_1(struct rev_info *revs) if (add_parents_to_list(revs, commit, &revs->commits) < 0) return NULL; } - if (commit->object.flags & SHOWN) - continue; - - if (revs->unpacked && has_sha1_pack(commit->object.sha1, - revs->ignore_packed)) - continue; - if (commit->object.flags & UNINTERESTING) - continue; - if (revs->min_age != -1 && (commit->date > revs->min_age)) + switch (simplify_commit(revs, commit)) { + case commit_ignore: continue; - if (revs->no_merges && - commit->parents && commit->parents->next) - continue; - if (!commit_match(commit, revs)) - continue; - if (revs->prune_fn && revs->dense) { - /* Commit without changes? */ - if (!(commit->object.flags & TREECHANGE)) { - /* drop merges unless we want parenthood */ - if (!revs->parents) - continue; - /* non-merge - always ignore it */ - if (!commit->parents || !commit->parents->next) - continue; - } - if (revs->parents && rewrite_parents(revs, commit) < 0) - return NULL; + case commit_error: + return NULL; + default: + return commit; } - return commit; } while (revs->commits); return NULL; } diff --git a/revision.h b/revision.h index 98a0a8f3fa..992e1e9dd5 100644 --- a/revision.h +++ b/revision.h @@ -3,19 +3,18 @@ #define SEEN (1u<<0) #define UNINTERESTING (1u<<1) -#define TREECHANGE (1u<<2) +#define TREESAME (1u<<2) #define SHOWN (1u<<3) #define TMP_MARK (1u<<4) /* for isolated cases; clean after use */ #define BOUNDARY (1u<<5) #define CHILD_SHOWN (1u<<6) #define ADDED (1u<<7) /* Parents already parsed and added? */ #define SYMMETRIC_LEFT (1u<<8) +#define TOPOSORT (1u<<9) /* In the active toposort list.. */ struct rev_info; struct log_info; -typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); - struct rev_info { /* Starting list */ struct commit_list *commits; @@ -27,10 +26,11 @@ struct rev_info { /* Basic information */ const char *prefix; void *prune_data; - prune_fn_t *prune_fn; + unsigned int early_output; /* Traversal flags */ unsigned int dense:1, + prune:1, no_merges:1, no_walk:1, remove_empty_trees:1, @@ -96,9 +96,6 @@ struct rev_info { struct diff_options diffopt; struct diff_options pruning; - topo_sort_set_fn_t topo_setter; - topo_sort_get_fn_t topo_getter; - struct reflog_walk_info *reflog_info; }; @@ -107,6 +104,8 @@ struct rev_info { #define REV_TREE_DIFFERENT 2 /* revision.c */ +typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *); +volatile show_early_output_fn_t show_early_output; extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); @@ -131,4 +130,12 @@ extern void add_object(struct object *obj, extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name); +enum commit_action { + commit_ignore, + commit_show, + commit_error +}; + +extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit); + #endif diff --git a/rsh.c b/rsh.c deleted file mode 100644 index 5754a230e2..0000000000 --- a/rsh.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "cache.h" -#include "rsh.h" -#include "quote.h" - -#define COMMAND_SIZE 4096 - -int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, - char *url, int rmt_argc, char **rmt_argv) -{ - char *host; - char *path; - int sv[2]; - char command[COMMAND_SIZE]; - char *posn; - int sizen; - int of; - int i; - pid_t pid; - - if (!strcmp(url, "-")) { - *fd_in = 0; - *fd_out = 1; - return 0; - } - - host = strstr(url, "//"); - if (host) { - host += 2; - path = strchr(host, '/'); - } else { - host = url; - path = strchr(host, ':'); - if (path) - *(path++) = '\0'; - } - if (!path) { - return error("Bad URL: %s", url); - } - /* $GIT_RSH <host> "env GIT_DIR=<path> <remote_prog> <args...>" */ - sizen = COMMAND_SIZE; - posn = command; - of = 0; - of |= add_to_string(&posn, &sizen, "env ", 0); - of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT "=", 0); - of |= add_to_string(&posn, &sizen, path, 1); - of |= add_to_string(&posn, &sizen, " ", 0); - of |= add_to_string(&posn, &sizen, remote_prog, 1); - - for ( i = 0 ; i < rmt_argc ; i++ ) { - of |= add_to_string(&posn, &sizen, " ", 0); - of |= add_to_string(&posn, &sizen, rmt_argv[i], 1); - } - - of |= add_to_string(&posn, &sizen, " -", 0); - - if ( of ) - return error("Command line too long"); - - if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) - return error("Couldn't create socket"); - - pid = fork(); - if (pid < 0) - return error("Couldn't fork"); - if (!pid) { - const char *ssh, *ssh_basename; - ssh = getenv("GIT_SSH"); - if (!ssh) ssh = "ssh"; - ssh_basename = strrchr(ssh, '/'); - if (!ssh_basename) - ssh_basename = ssh; - else - ssh_basename++; - close(sv[1]); - dup2(sv[0], 0); - dup2(sv[0], 1); - execlp(ssh, ssh_basename, host, command, NULL); - } - close(sv[0]); - *fd_in = sv[1]; - *fd_out = sv[1]; - return 0; -} diff --git a/rsh.h b/rsh.h deleted file mode 100644 index ee2f499291..0000000000 --- a/rsh.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef RSH_H -#define RSH_H - -int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, - char *url, int rmt_argc, char **rmt_argv); - -#endif diff --git a/run-command.c b/run-command.c index 7e779d33ee..476d00c218 100644 --- a/run-command.c +++ b/run-command.c @@ -17,8 +17,8 @@ static inline void dup_devnull(int to) int start_command(struct child_process *cmd) { - int need_in, need_out; - int fdin[2], fdout[2]; + int need_in, need_out, need_err; + int fdin[2], fdout[2], fderr[2]; need_in = !cmd->no_stdin && cmd->in < 0; if (need_in) { @@ -41,12 +41,26 @@ int start_command(struct child_process *cmd) cmd->close_out = 1; } + need_err = !cmd->no_stderr && cmd->err < 0; + if (need_err) { + if (pipe(fderr) < 0) { + if (need_in) + close_pair(fdin); + if (need_out) + close_pair(fdout); + return -ERR_RUN_COMMAND_PIPE; + } + cmd->err = fderr[0]; + } + cmd->pid = fork(); if (cmd->pid < 0) { if (need_in) close_pair(fdin); if (need_out) close_pair(fdout); + if (need_err) + close_pair(fderr); return -ERR_RUN_COMMAND_FORK; } @@ -73,6 +87,13 @@ int start_command(struct child_process *cmd) close(cmd->out); } + if (cmd->no_stderr) + dup_devnull(2); + else if (need_err) { + dup2(fderr[1], 2); + close_pair(fderr); + } + if (cmd->dir && chdir(cmd->dir)) die("exec %s: cd to %s failed (%s)", cmd->argv[0], cmd->dir, strerror(errno)); @@ -102,19 +123,17 @@ int start_command(struct child_process *cmd) else if (cmd->out > 1) close(cmd->out); + if (need_err) + close(fderr[1]); + return 0; } -int finish_command(struct child_process *cmd) +static int wait_or_whine(pid_t pid) { - if (cmd->close_in) - close(cmd->in); - if (cmd->close_out) - close(cmd->out); - for (;;) { int status, code; - pid_t waiting = waitpid(cmd->pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); if (waiting < 0) { if (errno == EINTR) @@ -122,7 +141,7 @@ int finish_command(struct child_process *cmd) error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (waiting != cmd->pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; @@ -136,6 +155,15 @@ int finish_command(struct child_process *cmd) } } +int finish_command(struct child_process *cmd) +{ + if (cmd->close_in) + close(cmd->in); + if (cmd->close_out) + close(cmd->out); + return wait_or_whine(cmd->pid); +} + int run_command(struct child_process *cmd) { int code = start_command(cmd); @@ -178,3 +206,34 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const cmd.env = env; return run_command(&cmd); } + +int start_async(struct async *async) +{ + int pipe_out[2]; + + if (pipe(pipe_out) < 0) + return error("cannot create pipe: %s", strerror(errno)); + + async->pid = fork(); + if (async->pid < 0) { + error("fork (async) failed: %s", strerror(errno)); + close_pair(pipe_out); + return -1; + } + if (!async->pid) { + close(pipe_out[0]); + exit(!!async->proc(pipe_out[1], async->data)); + } + async->out = pipe_out[0]; + close(pipe_out[1]); + return 0; +} + +int finish_async(struct async *async) +{ + int ret = 0; + + if (wait_or_whine(async->pid)) + ret = error("waitpid (async) failed"); + return ret; +} diff --git a/run-command.h b/run-command.h index 7958eb1e0b..1fc781d766 100644 --- a/run-command.h +++ b/run-command.h @@ -16,12 +16,14 @@ struct child_process { pid_t pid; int in; int out; + int err; const char *dir; const char *const *env; unsigned close_in:1; unsigned close_out:1; unsigned no_stdin:1; unsigned no_stdout:1; + unsigned no_stderr:1; unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned stdout_to_stderr:1; }; @@ -42,4 +44,26 @@ int run_command_v_opt_cd(const char **argv, int opt, const char *dir); */ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env); +/* + * The purpose of the following functions is to feed a pipe by running + * a function asynchronously and providing output that the caller reads. + * + * It is expected that no synchronization and mutual exclusion between + * the caller and the feed function is necessary so that the function + * can run in a thread without interfering with the caller. + */ +struct async { + /* + * proc writes to fd and closes it; + * returns 0 on success, non-zero on failure + */ + int (*proc)(int fd, void *data); + void *data; + int out; /* caller reads from here and closes it */ + pid_t pid; +}; + +int start_async(struct async *async); +int finish_async(struct async *async); + #endif diff --git a/send-pack.c b/send-pack.c deleted file mode 100644 index fd985ed28d..0000000000 --- a/send-pack.c +++ /dev/null @@ -1,444 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "pkt-line.h" -#include "run-command.h" -#include "remote.h" - -static const char send_pack_usage[] = -"git-send-pack [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" -" --all and explicit <ref> specification are mutually exclusive."; -static const char *receivepack = "git-receive-pack"; -static int verbose; -static int send_all; -static int force_update; -static int use_thin_pack; - -/* - * Make a pack stream and spit it out into file descriptor fd - */ -static int pack_objects(int fd, struct ref *refs) -{ - /* - * The child becomes pack-objects --revs; we feed - * the revision parameters to it via its stdin and - * let its stdout go back to the other end. - */ - const char *args[] = { - "pack-objects", - "--all-progress", - "--revs", - "--stdout", - NULL, - NULL, - }; - struct child_process po; - - if (use_thin_pack) - args[4] = "--thin"; - memset(&po, 0, sizeof(po)); - po.argv = args; - po.in = -1; - po.out = fd; - po.git_cmd = 1; - if (start_command(&po)) - die("git-pack-objects failed (%s)", strerror(errno)); - - /* - * We feed the pack-objects we just spawned with revision - * parameters by writing to the pipe. - */ - while (refs) { - char buf[42]; - - if (!is_null_sha1(refs->old_sha1) && - has_sha1_file(refs->old_sha1)) { - memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); - buf[0] = '^'; - buf[41] = '\n'; - if (!write_or_whine(po.in, buf, 42, - "send-pack: send refs")) - break; - } - if (!is_null_sha1(refs->new_sha1)) { - memcpy(buf, sha1_to_hex(refs->new_sha1), 40); - buf[40] = '\n'; - if (!write_or_whine(po.in, buf, 41, - "send-pack: send refs")) - break; - } - refs = refs->next; - } - - if (finish_command(&po)) - return error("pack-objects died with strange error"); - return 0; -} - -static void unmark_and_free(struct commit_list *list, unsigned int mark) -{ - while (list) { - struct commit_list *temp = list; - temp->item->object.flags &= ~mark; - list = temp->next; - free(temp); - } -} - -static int ref_newer(const unsigned char *new_sha1, - const unsigned char *old_sha1) -{ - struct object *o; - struct commit *old, *new; - struct commit_list *list, *used; - int found = 0; - - /* Both new and old must be commit-ish and new is descendant of - * old. Otherwise we require --force. - */ - o = deref_tag(parse_object(old_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - old = (struct commit *) o; - - o = deref_tag(parse_object(new_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - new = (struct commit *) o; - - if (parse_commit(new) < 0) - return 0; - - used = list = NULL; - commit_list_insert(new, &list); - while (list) { - new = pop_most_recent_commit(&list, 1); - commit_list_insert(new, &used); - if (new == old) { - found = 1; - break; - } - } - unmark_and_free(list, 1); - unmark_and_free(used, 1); - return found; -} - -static struct ref *local_refs, **local_tail; -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; - ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->new_sha1, sha1); - memcpy(ref->name, refname, len); - *local_tail = ref; - local_tail = &ref->next; - return 0; -} - -static void get_local_heads(void) -{ - local_tail = &local_refs; - for_each_ref(one_local_ref, NULL); -} - -static int receive_status(int in) -{ - char line[1000]; - int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) { - fprintf(stderr, "did not receive status back\n"); - return -1; - } - if (memcmp(line, "unpack ok\n", 10)) { - fputs(line, stderr); - ret = -1; - } - while (1) { - len = packet_read_line(in, line, sizeof(line)); - if (!len) - break; - if (len < 3 || - (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) { - fprintf(stderr, "protocol error: %s\n", line); - ret = -1; - break; - } - if (!memcmp(line, "ok", 2)) - continue; - fputs(line, stderr); - ret = -1; - } - return ret; -} - -static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec) -{ - struct ref *ref; - int new_refs; - int ret = 0; - int ask_for_status_report = 0; - int allow_deleting_refs = 0; - int expect_status_report = 0; - - /* No funny business with the matcher */ - remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); - get_local_heads(); - - /* Does the other end support the reporting? */ - if (server_supports("report-status")) - ask_for_status_report = 1; - if (server_supports("delete-refs")) - allow_deleting_refs = 1; - - /* match them up */ - if (!remote_tail) - remote_tail = &remote_refs; - if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, send_all)) - return -1; - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - - /* - * Finally, tell the other end! - */ - new_refs = 0; - for (ref = remote_refs; ref; ref = ref->next) { - char old_hex[60], *new_hex; - int will_delete_ref; - - if (!ref->peer_ref) - continue; - - - will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); - if (will_delete_ref && !allow_deleting_refs) { - error("remote does not support deleting refs"); - ret = -2; - continue; - } - if (!will_delete_ref && - !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { - if (verbose) - fprintf(stderr, "'%s': up-to-date\n", ref->name); - continue; - } - - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - if (!force_update && - !will_delete_ref && - !is_null_sha1(ref->old_sha1) && - !ref->force) { - if (!has_sha1_file(ref->old_sha1) || - !ref_newer(ref->peer_ref->new_sha1, - ref->old_sha1)) { - /* We do not have the remote ref, or - * we know that the remote ref is not - * an ancestor of what we are trying to - * push. Either way this can be losing - * commits at the remote end and likely - * we were not up to date to begin with. - */ - error("remote '%s' is not a strict " - "subset of local ref '%s'. " - "maybe you are not up-to-date and " - "need to pull first?", - ref->name, - ref->peer_ref->name); - ret = -2; - continue; - } - } - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - if (!will_delete_ref) - new_refs++; - strcpy(old_hex, sha1_to_hex(ref->old_sha1)); - new_hex = sha1_to_hex(ref->new_sha1); - - if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", - old_hex, new_hex, ref->name, 0, - "report-status"); - ask_for_status_report = 0; - expect_status_report = 1; - } - else - packet_write(out, "%s %s %s", - old_hex, new_hex, ref->name); - if (will_delete_ref) - fprintf(stderr, "deleting '%s'\n", ref->name); - else { - fprintf(stderr, "updating '%s'", ref->name); - if (strcmp(ref->name, ref->peer_ref->name)) - fprintf(stderr, " using '%s'", - ref->peer_ref->name); - fprintf(stderr, "\n from %s\n to %s\n", - old_hex, new_hex); - } - if (remote) { - struct refspec rs; - rs.src = ref->name; - rs.dst = NULL; - if (!remote_find_tracking(remote, &rs)) { - struct ref_lock *lock; - fprintf(stderr, " Also local %s\n", rs.dst); - if (will_delete_ref) { - if (delete_ref(rs.dst, NULL)) { - error("Failed to delete"); - } - } else { - lock = lock_any_ref_for_update(rs.dst, NULL, 0); - if (!lock) - error("Failed to lock"); - else - write_ref_sha1(lock, ref->new_sha1, - "update by push"); - } - free(rs.dst); - } - } - } - - packet_flush(out); - if (new_refs) - ret = pack_objects(out, remote_refs); - close(out); - - if (expect_status_report) { - if (receive_status(in)) - ret = -4; - } - - if (!new_refs && ret == 0) - fprintf(stderr, "Everything up-to-date\n"); - return ret; -} - -static void verify_remote_names(int nr_heads, char **heads) -{ - int i; - - for (i = 0; i < nr_heads; i++) { - const char *remote = strchr(heads[i], ':'); - - remote = remote ? (remote + 1) : heads[i]; - switch (check_ref_format(remote)) { - case 0: /* ok */ - case -2: /* ok but a single level -- that is fine for - * a match pattern. - */ - case -3: /* ok but ends with a pattern-match character */ - continue; - } - die("remote part of refspec is not a valid name in %s", - heads[i]); - } -} - -int main(int argc, char **argv) -{ - int i, nr_heads = 0; - char *dest = NULL; - char **heads = NULL; - int fd[2], ret; - pid_t pid; - char *remote_name = NULL; - struct remote *remote = NULL; - - setup_git_directory(); - git_config(git_default_config); - - argv++; - for (i = 1; i < argc; i++, argv++) { - char *arg = *argv; - - if (*arg == '-') { - if (!prefixcmp(arg, "--receive-pack=")) { - receivepack = arg + 15; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - receivepack = arg + 7; - continue; - } - if (!prefixcmp(arg, "--remote=")) { - remote_name = arg + 9; - continue; - } - if (!strcmp(arg, "--all")) { - send_all = 1; - continue; - } - if (!strcmp(arg, "--force")) { - force_update = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--thin")) { - use_thin_pack = 1; - continue; - } - usage(send_pack_usage); - } - if (!dest) { - dest = arg; - continue; - } - heads = argv; - nr_heads = argc - i; - break; - } - if (!dest) - usage(send_pack_usage); - if (heads && send_all) - usage(send_pack_usage); - verify_remote_names(nr_heads, heads); - - if (remote_name) { - remote = remote_get(remote_name); - if (!remote_has_uri(remote, dest)) { - die("Destination %s is not a uri for %s", - dest, remote_name); - } - } - - pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0); - if (pid < 0) - return 1; - ret = send_pack(fd[0], fd[1], remote, nr_heads, heads); - close(fd[0]); - close(fd[1]); - ret |= finish_connect(pid); - return !!ret; -} diff --git a/send-pack.h b/send-pack.h new file mode 100644 index 0000000000..8ff1dc3539 --- /dev/null +++ b/send-pack.h @@ -0,0 +1,18 @@ +#ifndef SEND_PACK_H +#define SEND_PACK_H + +struct send_pack_args { + const char *receivepack; + unsigned verbose:1, + send_all:1, + send_mirror:1, + force_update:1, + use_thin_pack:1, + dry_run:1; +}; + +int send_pack(struct send_pack_args *args, + const char *dest, struct remote *remote, + int nr_heads, const char **heads); + +#endif diff --git a/server-info.c b/server-info.c index 0d1312ca56..a051e49a9e 100644 --- a/server-info.c +++ b/server-info.c @@ -35,7 +35,7 @@ static int update_info_refs(int force) safe_create_leading_directories(path0); info_ref_fp = fopen(path1, "w"); if (!info_ref_fp) - return error("unable to update %s", path0); + return error("unable to update %s", path1); for_each_ref(add_info_ref, NULL); fclose(info_ref_fp); adjust_shared_perm(path1); @@ -59,7 +59,7 @@ const char *prefix_path(const char *prefix, int len, const char *path) const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) { static char path[PATH_MAX]; - if (!pfx || !*pfx || arg[0] == '/') + if (!pfx || !*pfx || is_absolute_path(arg)) return arg; memcpy(path, pfx, pfx_len); strcpy(path + pfx_len, arg); @@ -206,6 +206,22 @@ static const char *set_work_tree(const char *dir) return NULL; } +void setup_work_tree(void) +{ + const char *work_tree, *git_dir; + static int initialized = 0; + + if (initialized) + return; + work_tree = get_git_work_tree(); + git_dir = get_git_dir(); + if (!is_absolute_path(git_dir)) + set_git_dir(make_absolute_path(git_dir)); + if (!work_tree || chdir(work_tree)) + die("This operation must be run in a work tree"); + initialized = 1; +} + /* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. diff --git a/sha1_file.c b/sha1_file.c index 95b5a403d8..b0c24356ae 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -25,8 +25,10 @@ #ifdef NO_C99_FORMAT #define SZ_FMT "lu" +static unsigned long sz_fmt(size_t s) { return (unsigned long)s; } #else #define SZ_FMT "zu" +static size_t sz_fmt(size_t s) { return s; } #endif const unsigned char null_sha1[20]; @@ -86,7 +88,7 @@ int safe_create_leading_directories(char *path) char *pos = path; struct stat st; - if (*pos == '/') + if (is_absolute_path(path)) pos++; while (pos) { @@ -253,7 +255,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative int entlen = pfxlen + 43; int base_len = -1; - if (*entry != '/' && relative_base) { + if (!is_absolute_path(entry) && relative_base) { /* Relative alt-odb */ if (base_len < 0) base_len = strlen(relative_base) + 1; @@ -262,7 +264,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative } ent = xmalloc(sizeof(*ent) + entlen); - if (*entry != '/' && relative_base) { + if (!is_absolute_path(entry) && relative_base) { memcpy(ent->base, relative_base, base_len - 1); ent->base[base_len - 1] = '/'; memcpy(ent->base + base_len, entry, len); @@ -333,7 +335,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep, while (cp < ep && *cp != sep) cp++; if (last != cp) { - if ((*last != '/') && depth) { + if (!is_absolute_path(last) && depth) { error("%s: ignoring relative alternate object store %s", relative_base, last); } else { @@ -423,9 +425,9 @@ void pack_report(void) "pack_report: getpagesize() = %10" SZ_FMT "\n" "pack_report: core.packedGitWindowSize = %10" SZ_FMT "\n" "pack_report: core.packedGitLimit = %10" SZ_FMT "\n", - (size_t) getpagesize(), - packed_git_window_size, - packed_git_limit); + sz_fmt(getpagesize()), + sz_fmt(packed_git_window_size), + sz_fmt(packed_git_limit)); fprintf(stderr, "pack_report: pack_used_ctr = %10u\n" "pack_report: pack_mmap_calls = %10u\n" @@ -435,7 +437,7 @@ void pack_report(void) pack_used_ctr, pack_mmap_calls, pack_open_windows, peak_pack_open_windows, - pack_mapped, peak_pack_mapped); + sz_fmt(pack_mapped), sz_fmt(peak_pack_mapped)); } static int check_packed_git_idx(const char *path, struct packed_git *p) @@ -1493,11 +1495,8 @@ found_cache_entry: ent->lru.next->prev = ent->lru.prev; ent->lru.prev->next = ent->lru.next; delta_base_cached -= ent->size; - } - else { - ret = xmalloc(ent->size + 1); - memcpy(ret, ent->data, ent->size); - ((char *)ret)[ent->size] = 0; + } else { + ret = xmemdupz(ent->data, ent->size); } *type = ent->type; *base_size = ent->size; @@ -1686,22 +1685,22 @@ off_t find_pack_entry_one(const unsigned char *sha1, return 0; } -static int matches_pack_name(struct packed_git *p, const char *ig) +int matches_pack_name(struct packed_git *p, const char *name) { const char *last_c, *c; - if (!strcmp(p->pack_name, ig)) - return 0; + if (!strcmp(p->pack_name, name)) + return 1; for (c = p->pack_name, last_c = c; *c;) if (*c == '/') last_c = ++c; else ++c; - if (!strcmp(last_c, ig)) - return 0; + if (!strcmp(last_c, name)) + return 1; - return 1; + return 0; } static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) @@ -1719,7 +1718,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons if (ignore_packed) { const char **ig; for (ig = ignore_packed; *ig; ig++) - if (!matches_pack_name(p, *ig)) + if (matches_pack_name(p, *ig)) break; if (*ig) goto next; @@ -1874,12 +1873,9 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type, co = find_cached_object(sha1); if (co) { - buf = xmalloc(co->size + 1); - memcpy(buf, co->buf, co->size); - ((char*)buf)[co->size] = 0; *type = co->type; *size = co->size; - return buf; + return xmemdupz(co->buf, co->size); } buf = read_packed_sha1(sha1, type, size); @@ -2304,68 +2300,25 @@ int has_sha1_file(const unsigned char *sha1) return find_sha1_file(sha1, &st) ? 1 : 0; } -/* - * reads from fd as long as possible into a supplied buffer of size bytes. - * If necessary the buffer's size is increased using realloc() - * - * returns 0 if anything went fine and -1 otherwise - * - * The buffer is always NUL-terminated, not including it in returned size. - * - * NOTE: both buf and size may change, but even when -1 is returned - * you still have to free() it yourself. - */ -int read_fd(int fd, char **return_buf, unsigned long *return_size) -{ - char *buf = *return_buf; - unsigned long size = *return_size; - ssize_t iret; - unsigned long off = 0; - - if (!buf || size <= 1) { - size = 1024; - buf = xrealloc(buf, size); - } - - do { - iret = xread(fd, buf + off, (size - 1) - off); - if (iret > 0) { - off += iret; - if (off == size - 1) { - size = alloc_nr(size); - buf = xrealloc(buf, size); - } - } - } while (iret > 0); - - buf[off] = '\0'; - - *return_buf = buf; - *return_size = off; - - if (iret < 0) - return -1; - return 0; -} - int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) { - unsigned long size = 4096; - char *buf = xmalloc(size); + struct strbuf buf; int ret; - if (read_fd(fd, &buf, &size)) { - free(buf); + strbuf_init(&buf, 0); + if (strbuf_read(&buf, fd, 4096) < 0) { + strbuf_release(&buf); return -1; } if (!type) type = blob_type; if (write_object) - ret = write_sha1_file(buf, size, type, sha1); + ret = write_sha1_file(buf.buf, buf.len, type, sha1); else - ret = hash_sha1_file(buf, size, type, sha1); - free(buf); + ret = hash_sha1_file(buf.buf, buf.len, type, sha1); + strbuf_release(&buf); + return ret; } @@ -2387,12 +2340,11 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, * Convert blobs to git internal format */ if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) { - unsigned long nsize = size; - char *nbuf = convert_to_git(path, buf, &nsize); - if (nbuf) { + struct strbuf nbuf; + strbuf_init(&nbuf, 0); + if (convert_to_git(path, buf, size, &nbuf)) { munmap(buf, size); - size = nsize; - buf = nbuf; + buf = strbuf_detach(&nbuf, &size); re_allocated = 1; } } @@ -1,6 +1,7 @@ #include "cache.h" #include "quote.h" #include "exec_cmd.h" +#include "strbuf.h" static int do_generic_cmd(const char *me, char *arg) { @@ -18,12 +19,28 @@ static int do_generic_cmd(const char *me, char *arg) return execv_git_cmd(my_argv); } +static int do_cvs_cmd(const char *me, char *arg) +{ + const char *cvsserver_argv[3] = { + "cvsserver", "server", NULL + }; + + if (!arg || strcmp(arg, "server")) + die("git-cvsserver only handles server: %s", arg); + + setup_path(NULL); + + return execv_git_cmd(cvsserver_argv); +} + + static struct commands { const char *name; int (*exec)(const char *me, char *arg); } cmd_list[] = { { "git-receive-pack", do_generic_cmd }, { "git-upload-pack", do_generic_cmd }, + { "cvs", do_cvs_cmd }, { NULL }, }; @@ -32,8 +49,10 @@ int main(int argc, char **argv) char *prog; struct commands *cmd; + if (argc == 2 && !strcmp(argv[1], "cvs server")) + argv--; /* We want to see "-c cmd args", and nothing else */ - if (argc != 3 || strcmp(argv[1], "-c")) + else if (argc != 3 || strcmp(argv[1], "-c")) die("What do you think I am? A shell?"); prog = argv[2]; diff --git a/show-index.c b/show-index.c index 57ed9e87b7..7253991fff 100644 --- a/show-index.c +++ b/show-index.c @@ -68,7 +68,7 @@ int main(int argc, char **argv) ntohl(off64[1]); off64_nr++; } - printf("%llu %s (%08x)\n", (unsigned long long) offset, + printf("%" PRIuMAX " %s (%08x)\n", (uintmax_t) offset, sha1_to_hex(entries[i].sha1), ntohl(entries[i].crc)); } diff --git a/sideband.c b/sideband.c index 277fa3c10d..756bbc28d7 100644 --- a/sideband.c +++ b/sideband.c @@ -11,13 +11,19 @@ * stream, aka "verbose"). A message over band #3 is a signal that * the remote died unexpectedly. A flush() concludes the stream. */ + +#define PREFIX "remote:" +#define SUFFIX "\033[K" /* change to " " if ANSI sequences don't work */ + int recv_sideband(const char *me, int in_stream, int out, int err) { - char buf[7 + LARGE_PACKET_MAX + 1]; - strcpy(buf, "remote:"); + unsigned pf = strlen(PREFIX); + unsigned sf = strlen(SUFFIX); + char buf[pf + LARGE_PACKET_MAX + sf + 1]; + memcpy(buf, PREFIX, pf); while (1) { int band, len; - len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX); + len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX); if (len == 0) break; if (len < 1) { @@ -25,20 +31,52 @@ int recv_sideband(const char *me, int in_stream, int out, int err) safe_write(err, buf, len); return SIDEBAND_PROTOCOL_ERROR; } - band = buf[7] & 0xff; + band = buf[pf] & 0xff; len--; switch (band) { case 3: - buf[7] = ' '; - buf[8+len] = '\n'; - safe_write(err, buf, 8+len+1); + buf[pf] = ' '; + buf[pf+1+len] = '\n'; + safe_write(err, buf, pf+1+len+1); return SIDEBAND_REMOTE_ERROR; case 2: - buf[7] = ' '; - safe_write(err, buf, 8+len); + buf[pf] = ' '; + len += pf+1; + while (1) { + int brk = pf+1; + + /* Break the buffer into separate lines. */ + while (brk < len) { + brk++; + if (buf[brk-1] == '\n' || + buf[brk-1] == '\r') + break; + } + + /* + * Let's insert a suffix to clear the end + * of the screen line, but only if current + * line data actually contains something. + */ + if (brk > pf+1 + 1) { + char save[sf]; + memcpy(save, buf + brk, sf); + buf[brk + sf - 1] = buf[brk - 1]; + memcpy(buf + brk - 1, SUFFIX, sf); + safe_write(err, buf, brk + sf); + memcpy(buf + brk, save, sf); + } else + safe_write(err, buf, brk); + + if (brk < len) { + memmove(buf + pf+1, buf + brk, len - brk); + len = len - brk + pf+1; + } else + break; + } continue; case 1: - safe_write(out, buf+8, len); + safe_write(out, buf + pf+1, len); continue; default: len = sprintf(buf, diff --git a/ssh-fetch.c b/ssh-fetch.c deleted file mode 100644 index bdf51a7a14..0000000000 --- a/ssh-fetch.c +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef COUNTERPART_ENV_NAME -#define COUNTERPART_ENV_NAME "GIT_SSH_UPLOAD" -#endif -#ifndef COUNTERPART_PROGRAM_NAME -#define COUNTERPART_PROGRAM_NAME "git-ssh-upload" -#endif -#ifndef MY_PROGRAM_NAME -#define MY_PROGRAM_NAME "git-ssh-fetch" -#endif - -#include "cache.h" -#include "commit.h" -#include "rsh.h" -#include "fetch.h" -#include "refs.h" - -static int fd_in; -static int fd_out; - -static unsigned char remote_version; -static unsigned char local_version = 1; - -static int prefetches; - -static struct object_list *in_transit; -static struct object_list **end_of_transit = &in_transit; - -void prefetch(unsigned char *sha1) -{ - char type = 'o'; - struct object_list *node; - if (prefetches > 100) { - fetch(in_transit->item->sha1); - } - node = xmalloc(sizeof(struct object_list)); - node->next = NULL; - node->item = lookup_unknown_object(sha1); - *end_of_transit = node; - end_of_transit = &node->next; - /* XXX: what if these writes fail? */ - write_in_full(fd_out, &type, 1); - write_in_full(fd_out, sha1, 20); - prefetches++; -} - -static char conn_buf[4096]; -static size_t conn_buf_posn; - -int fetch(unsigned char *sha1) -{ - int ret; - signed char remote; - struct object_list *temp; - - if (hashcmp(sha1, in_transit->item->sha1)) { - /* we must have already fetched it to clean the queue */ - return has_sha1_file(sha1) ? 0 : -1; - } - prefetches--; - temp = in_transit; - in_transit = in_transit->next; - if (!in_transit) - end_of_transit = &in_transit; - free(temp); - - if (conn_buf_posn) { - remote = conn_buf[0]; - memmove(conn_buf, conn_buf + 1, --conn_buf_posn); - } else { - if (xread(fd_in, &remote, 1) < 1) - return -1; - } - /* fprintf(stderr, "Got %d\n", remote); */ - if (remote < 0) - return remote; - ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn); - if (!ret) - pull_say("got %s\n", sha1_to_hex(sha1)); - return ret; -} - -static int get_version(void) -{ - char type = 'v'; - if (write_in_full(fd_out, &type, 1) != 1 || - write_in_full(fd_out, &local_version, 1)) { - return error("Couldn't request version from remote end"); - } - if (xread(fd_in, &remote_version, 1) < 1) { - return error("Couldn't read version from remote end"); - } - return 0; -} - -int fetch_ref(char *ref, unsigned char *sha1) -{ - signed char remote; - char type = 'r'; - int length = strlen(ref) + 1; - if (write_in_full(fd_out, &type, 1) != 1 || - write_in_full(fd_out, ref, length) != length) - return -1; - - if (read_in_full(fd_in, &remote, 1) != 1) - return -1; - if (remote < 0) - return remote; - if (read_in_full(fd_in, sha1, 20) != 20) - return -1; - return 0; -} - -static const char ssh_fetch_usage[] = - MY_PROGRAM_NAME - " [-c] [-t] [-a] [-v] [--recover] [-w ref] commit-id url"; -int main(int argc, char **argv) -{ - const char *write_ref = NULL; - char *commit_id; - char *url; - int arg = 1; - const char *prog; - - prog = getenv("GIT_SSH_PUSH"); - if (!prog) prog = "git-ssh-upload"; - - setup_git_directory(); - git_config(git_default_config); - - while (arg < argc && argv[arg][0] == '-') { - if (argv[arg][1] == 't') { - get_tree = 1; - } else if (argv[arg][1] == 'c') { - get_history = 1; - } else if (argv[arg][1] == 'a') { - get_all = 1; - get_tree = 1; - get_history = 1; - } else if (argv[arg][1] == 'v') { - get_verbosely = 1; - } else if (argv[arg][1] == 'w') { - write_ref = argv[arg + 1]; - arg++; - } else if (!strcmp(argv[arg], "--recover")) { - get_recover = 1; - } - arg++; - } - if (argc < arg + 2) { - usage(ssh_fetch_usage); - return 1; - } - commit_id = argv[arg]; - url = argv[arg + 1]; - - if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1)) - return 1; - - if (get_version()) - return 1; - - if (pull(1, &commit_id, &write_ref, url)) - return 1; - - return 0; -} diff --git a/ssh-pull.c b/ssh-pull.c deleted file mode 100644 index 868ce4d41f..0000000000 --- a/ssh-pull.c +++ /dev/null @@ -1,4 +0,0 @@ -#define COUNTERPART_ENV_NAME "GIT_SSH_PUSH" -#define COUNTERPART_PROGRAM_NAME "git-ssh-push" -#define MY_PROGRAM_NAME "git-ssh-pull" -#include "ssh-fetch.c" diff --git a/ssh-push.c b/ssh-push.c deleted file mode 100644 index a562df1b31..0000000000 --- a/ssh-push.c +++ /dev/null @@ -1,4 +0,0 @@ -#define COUNTERPART_ENV_NAME "GIT_SSH_PULL" -#define COUNTERPART_PROGRAM_NAME "git-ssh-pull" -#define MY_PROGRAM_NAME "git-ssh-push" -#include "ssh-upload.c" diff --git a/ssh-upload.c b/ssh-upload.c deleted file mode 100644 index 20c35f03dd..0000000000 --- a/ssh-upload.c +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef COUNTERPART_ENV_NAME -#define COUNTERPART_ENV_NAME "GIT_SSH_FETCH" -#endif -#ifndef COUNTERPART_PROGRAM_NAME -#define COUNTERPART_PROGRAM_NAME "git-ssh-fetch" -#endif -#ifndef MY_PROGRAM_NAME -#define MY_PROGRAM_NAME "git-ssh-upload" -#endif - -#include "cache.h" -#include "rsh.h" -#include "refs.h" - -static unsigned char local_version = 1; -static unsigned char remote_version; - -static int verbose; - -static int serve_object(int fd_in, int fd_out) { - ssize_t size; - unsigned char sha1[20]; - signed char remote; - - size = read_in_full(fd_in, sha1, 20); - if (size < 0) { - perror("git-ssh-upload: read "); - return -1; - } - if (!size) - return -1; - - if (verbose) - fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); - - remote = 0; - - if (!has_sha1_file(sha1)) { - fprintf(stderr, "git-ssh-upload: could not find %s\n", - sha1_to_hex(sha1)); - remote = -1; - } - - if (write_in_full(fd_out, &remote, 1) != 1) - return 0; - - if (remote < 0) - return 0; - - return write_sha1_to_fd(fd_out, sha1); -} - -static int serve_version(int fd_in, int fd_out) -{ - if (xread(fd_in, &remote_version, 1) < 1) - return -1; - write_in_full(fd_out, &local_version, 1); - return 0; -} - -static int serve_ref(int fd_in, int fd_out) -{ - char ref[PATH_MAX]; - unsigned char sha1[20]; - int posn = 0; - signed char remote = 0; - do { - if (posn >= PATH_MAX || xread(fd_in, ref + posn, 1) < 1) - return -1; - posn++; - } while (ref[posn - 1]); - - if (verbose) - fprintf(stderr, "Serving %s\n", ref); - - if (get_ref_sha1(ref, sha1)) - remote = -1; - if (write_in_full(fd_out, &remote, 1) != 1) - return 0; - if (remote) - return 0; - write_in_full(fd_out, sha1, 20); - return 0; -} - - -static void service(int fd_in, int fd_out) { - char type; - ssize_t retval; - do { - retval = xread(fd_in, &type, 1); - if (retval < 1) { - if (retval < 0) - perror("git-ssh-upload: read "); - return; - } - if (type == 'v' && serve_version(fd_in, fd_out)) - return; - if (type == 'o' && serve_object(fd_in, fd_out)) - return; - if (type == 'r' && serve_ref(fd_in, fd_out)) - return; - } while (1); -} - -static const char ssh_push_usage[] = - MY_PROGRAM_NAME " [-c] [-t] [-a] [-w ref] commit-id url"; - -int main(int argc, char **argv) -{ - int arg = 1; - char *commit_id; - char *url; - int fd_in, fd_out; - const char *prog; - unsigned char sha1[20]; - char hex[41]; - - prog = getenv(COUNTERPART_ENV_NAME); - if (!prog) prog = COUNTERPART_PROGRAM_NAME; - - setup_git_directory(); - - while (arg < argc && argv[arg][0] == '-') { - if (argv[arg][1] == 'w') - arg++; - arg++; - } - if (argc < arg + 2) - usage(ssh_push_usage); - commit_id = argv[arg]; - url = argv[arg + 1]; - if (get_sha1(commit_id, sha1)) - die("Not a valid object name %s", commit_id); - memcpy(hex, sha1_to_hex(sha1), sizeof(hex)); - argv[arg] = hex; - - if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1)) - return 1; - - service(fd_in, fd_out); - return 0; -} @@ -1,41 +1,234 @@ #include "cache.h" -#include "strbuf.h" -void strbuf_init(struct strbuf *sb) { - sb->buf = NULL; - sb->eof = sb->alloc = sb->len = 0; +/* + * Used as the default ->buf value, so that people can always assume + * buf is non NULL and ->buf is NUL terminated even for a freshly + * initialized strbuf. + */ +char strbuf_slopbuf[1]; + +void strbuf_init(struct strbuf *sb, size_t hint) +{ + sb->alloc = sb->len = 0; + sb->buf = strbuf_slopbuf; + if (hint) + strbuf_grow(sb, hint); +} + +void strbuf_release(struct strbuf *sb) +{ + if (sb->alloc) { + free(sb->buf); + strbuf_init(sb, 0); + } +} + +char *strbuf_detach(struct strbuf *sb, size_t *sz) +{ + char *res = sb->alloc ? sb->buf : NULL; + if (sz) + *sz = sb->len; + strbuf_init(sb, 0); + return res; } -static void strbuf_begin(struct strbuf *sb) { - free(sb->buf); - strbuf_init(sb); +void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc) +{ + strbuf_release(sb); + sb->buf = buf; + sb->len = len; + sb->alloc = alloc; + strbuf_grow(sb, 0); + sb->buf[sb->len] = '\0'; } -static void inline strbuf_add(struct strbuf *sb, int ch) { - if (sb->alloc <= sb->len) { - sb->alloc = sb->alloc * 3 / 2 + 16; - sb->buf = xrealloc(sb->buf, sb->alloc); +void strbuf_grow(struct strbuf *sb, size_t extra) +{ + if (sb->len + extra + 1 <= sb->len) + die("you want to use way too much memory"); + if (!sb->alloc) + sb->buf = NULL; + ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc); +} + +void strbuf_rtrim(struct strbuf *sb) +{ + while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1])) + sb->len--; + sb->buf[sb->len] = '\0'; +} + +int strbuf_cmp(struct strbuf *a, struct strbuf *b) +{ + int cmp; + if (a->len < b->len) { + cmp = memcmp(a->buf, b->buf, a->len); + return cmp ? cmp : -1; + } else { + cmp = memcmp(a->buf, b->buf, b->len); + return cmp ? cmp : a->len != b->len; } - sb->buf[sb->len++] = ch; } -static void strbuf_end(struct strbuf *sb) { - strbuf_add(sb, 0); +void strbuf_splice(struct strbuf *sb, size_t pos, size_t len, + const void *data, size_t dlen) +{ + if (pos + len < pos) + die("you want to use way too much memory"); + if (pos > sb->len) + die("`pos' is too far after the end of the buffer"); + if (pos + len > sb->len) + die("`pos + len' is too far after the end of the buffer"); + + if (dlen >= len) + strbuf_grow(sb, dlen - len); + memmove(sb->buf + pos + dlen, + sb->buf + pos + len, + sb->len - pos - len); + memcpy(sb->buf + pos, data, dlen); + strbuf_setlen(sb, sb->len + dlen - len); } -void read_line(struct strbuf *sb, FILE *fp, int term) { - int ch; - strbuf_begin(sb); - if (feof(fp)) { - sb->eof = 1; - return; +void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) +{ + strbuf_splice(sb, pos, 0, data, len); +} + +void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) +{ + strbuf_splice(sb, pos, len, NULL, 0); +} + +void strbuf_add(struct strbuf *sb, const void *data, size_t len) +{ + strbuf_grow(sb, len); + memcpy(sb->buf + sb->len, data, len); + strbuf_setlen(sb, sb->len + len); +} + +void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len) +{ + strbuf_grow(sb, len); + memcpy(sb->buf + sb->len, sb->buf + pos, len); + strbuf_setlen(sb, sb->len + len); +} + +void strbuf_addf(struct strbuf *sb, const char *fmt, ...) +{ + int len; + va_list ap; + + if (!strbuf_avail(sb)) + strbuf_grow(sb, 64); + va_start(ap, fmt); + len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); + va_end(ap); + if (len < 0) + die("your vsnprintf is broken"); + if (len > strbuf_avail(sb)) { + strbuf_grow(sb, len); + va_start(ap, fmt); + len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); + va_end(ap); + if (len > strbuf_avail(sb)) { + die("this should not happen, your snprintf is broken"); + } + } + strbuf_setlen(sb, sb->len + len); +} + +void strbuf_expand(struct strbuf *sb, const char *format, + const char **placeholders, expand_fn_t fn, void *context) +{ + for (;;) { + const char *percent, **p; + + percent = strchrnul(format, '%'); + strbuf_add(sb, format, percent - format); + if (!*percent) + break; + format = percent + 1; + + for (p = placeholders; *p; p++) { + if (!prefixcmp(format, *p)) + break; + } + if (*p) { + fn(sb, *p, context); + format += strlen(*p); + } else + strbuf_addch(sb, '%'); + } +} + +size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) +{ + size_t res; + + strbuf_grow(sb, size); + res = fread(sb->buf + sb->len, 1, size, f); + if (res > 0) { + strbuf_setlen(sb, sb->len + res); + } + return res; +} + +ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) +{ + size_t oldlen = sb->len; + + strbuf_grow(sb, hint ? hint : 8192); + for (;;) { + ssize_t cnt; + + cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1); + if (cnt < 0) { + strbuf_setlen(sb, oldlen); + return -1; + } + if (!cnt) + break; + sb->len += cnt; + strbuf_grow(sb, 8192); } + + sb->buf[sb->len] = '\0'; + return sb->len - oldlen; +} + +int strbuf_getline(struct strbuf *sb, FILE *fp, int term) +{ + int ch; + + strbuf_grow(sb, 0); + if (feof(fp)) + return EOF; + + strbuf_reset(sb); while ((ch = fgetc(fp)) != EOF) { if (ch == term) break; - strbuf_add(sb, ch); + strbuf_grow(sb, 1); + sb->buf[sb->len++] = ch; } if (ch == EOF && sb->len == 0) - sb->eof = 1; - strbuf_end(sb); + return EOF; + + sb->buf[sb->len] = '\0'; + return 0; +} + +int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) +{ + int fd, len; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + len = strbuf_read(sb, fd, hint); + close(fd); + if (len < 0) + return -1; + + return len; } @@ -1,13 +1,121 @@ #ifndef STRBUF_H #define STRBUF_H + +/* + * Strbuf's can be use in many ways: as a byte array, or to store arbitrary + * long, overflow safe strings. + * + * Strbufs has some invariants that are very important to keep in mind: + * + * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to + * build complex strings/buffers whose final size isn't easily known. + * + * It is NOT legal to copy the ->buf pointer away. + * `strbuf_detach' is the operation that detachs a buffer from its shell + * while keeping the shell valid wrt its invariants. + * + * 2. the ->buf member is a byte array that has at least ->len + 1 bytes + * allocated. The extra byte is used to store a '\0', allowing the ->buf + * member to be a valid C-string. Every strbuf function ensure this + * invariant is preserved. + * + * Note that it is OK to "play" with the buffer directly if you work it + * that way: + * + * strbuf_grow(sb, SOME_SIZE); + * ... Here, the memory array starting at sb->buf, and of length + * ... strbuf_avail(sb) is all yours, and you are sure that + * ... strbuf_avail(sb) is at least SOME_SIZE. + * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); + * + * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb). + * + * Doing so is safe, though if it has to be done in many places, adding the + * missing API to the strbuf module is the way to go. + * + * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1 + * even if it's true in the current implementation. Alloc is somehow a + * "private" member that should not be messed with. + */ + +#include <assert.h> + +extern char strbuf_slopbuf[]; struct strbuf { - int alloc; - int len; - int eof; + size_t alloc; + size_t len; char *buf; }; -extern void strbuf_init(struct strbuf *); -extern void read_line(struct strbuf *, FILE *, int); +#define STRBUF_INIT { 0, 0, strbuf_slopbuf } + +/*----- strbuf life cycle -----*/ +extern void strbuf_init(struct strbuf *, size_t); +extern void strbuf_release(struct strbuf *); +extern char *strbuf_detach(struct strbuf *, size_t *); +extern void strbuf_attach(struct strbuf *, void *, size_t, size_t); +static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) { + struct strbuf tmp = *a; + *a = *b; + *b = tmp; +} + +/*----- strbuf size related -----*/ +static inline size_t strbuf_avail(struct strbuf *sb) { + return sb->alloc ? sb->alloc - sb->len - 1 : 0; +} + +extern void strbuf_grow(struct strbuf *, size_t); + +static inline void strbuf_setlen(struct strbuf *sb, size_t len) { + if (!sb->alloc) + strbuf_grow(sb, 0); + assert(len < sb->alloc); + sb->len = len; + sb->buf[len] = '\0'; +} +#define strbuf_reset(sb) strbuf_setlen(sb, 0) + +/*----- content related -----*/ +extern void strbuf_rtrim(struct strbuf *); +extern int strbuf_cmp(struct strbuf *, struct strbuf *); + +/*----- add data in your buffer -----*/ +static inline void strbuf_addch(struct strbuf *sb, int c) { + strbuf_grow(sb, 1); + sb->buf[sb->len++] = c; + sb->buf[sb->len] = '\0'; +} + +extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t); +extern void strbuf_remove(struct strbuf *, size_t pos, size_t len); + +/* splice pos..pos+len with given data */ +extern void strbuf_splice(struct strbuf *, size_t pos, size_t len, + const void *, size_t); + +extern void strbuf_add(struct strbuf *, const void *, size_t); +static inline void strbuf_addstr(struct strbuf *sb, const char *s) { + strbuf_add(sb, s, strlen(s)); +} +static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) { + strbuf_add(sb, sb2->buf, sb2->len); +} +extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); + +typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context); +extern void strbuf_expand(struct strbuf *sb, const char *format, const char **placeholders, expand_fn_t fn, void *context); + +__attribute__((format(printf,2,3))) +extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); + +extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); +/* XXX: if read fails, any partial read is undone */ +extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); +extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); + +extern int strbuf_getline(struct strbuf *, FILE *, int); + +extern void stripspace(struct strbuf *buf, int skip_comments); #endif /* STRBUF_H */ diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index a839f4e074..cb860296ed 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -42,7 +42,12 @@ test_expect_success check ' git diff --raw --exit-code :test :test.i && id=$(git rev-parse --verify :test) && embedded=$(sed -ne "$script" test.i) && - test "z$id" = "z$embedded" + test "z$id" = "z$embedded" && + + git cat-file blob :test.t > test.r && + + ./rot13.sh < test.o > test.t && + cmp test.r test.t ' # If an expanded ident ever gets into the repository, we want to make sure that diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh new file mode 100755 index 0000000000..462fdf262f --- /dev/null +++ b/t/t0040-parse-options.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes Schindelin +# + +test_description='our own option parser' + +. ./test-lib.sh + +cat > expect.err << EOF +usage: test-parse-options <options> + + -b, --boolean get a boolean + -i, --integer <n> get a integer + -j <n> get a integer, too + +string options + -s, --string <string> + get a string + --string2 <str> get another string + --st <st> get another string (pervert ordering) + +EOF + +test_expect_success 'test help' ' + ! test-parse-options -h > output 2> output.err && + test ! -s output && + git diff expect.err output.err +' + +cat > expect << EOF +boolean: 2 +integer: 1729 +string: 123 +EOF + +test_expect_success 'short options' ' + test-parse-options -s123 -b -i 1729 -b > output 2> output.err && + git diff expect output && + test ! -s output.err +' +cat > expect << EOF +boolean: 2 +integer: 1729 +string: 321 +EOF + +test_expect_success 'long options' ' + test-parse-options --boolean --integer 1729 --boolean --string2=321 \ + > output 2> output.err && + test ! -s output.err && + git diff expect output +' + +cat > expect << EOF +boolean: 1 +integer: 13 +string: 123 +arg 00: a1 +arg 01: b1 +arg 02: --boolean +EOF + +test_expect_success 'intermingled arguments' ' + test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ + > output 2> output.err && + test ! -s output.err && + git diff expect output +' + +cat > expect << EOF +boolean: 0 +integer: 2 +string: (not set) +EOF + +test_expect_success 'unambiguously abbreviated option' ' + test-parse-options --int 2 --boolean --no-bo > output 2> output.err && + test ! -s output.err && + git diff expect output +' + +test_expect_success 'unambiguously abbreviated option with "="' ' + test-parse-options --int=2 > output 2> output.err && + test ! -s output.err && + git diff expect output +' + +test_expect_failure 'ambiguously abbreviated option' ' + test-parse-options --strin 123; + test $? != 129 +' + +cat > expect << EOF +boolean: 0 +integer: 0 +string: 123 +EOF + +test_expect_success 'non ambiguous option (after two options it abbreviates)' ' + test-parse-options --st 123 > output 2> output.err && + test ! -s output.err && + git diff expect output +' + +test_done diff --git a/t/t2008-checkout-subdir.sh b/t/t2008-checkout-subdir.sh new file mode 100755 index 0000000000..f78945ed8e --- /dev/null +++ b/t/t2008-checkout-subdir.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# +# Copyright (c) 2007 David Symonds + +test_description='git checkout from subdirectories' + +. ./test-lib.sh + +test_expect_success setup ' + + echo "base" > file0 && + git add file0 && + mkdir dir1 && + echo "hello" > dir1/file1 && + git add dir1/file1 && + mkdir dir2 && + echo "bonjour" > dir2/file2 && + git add dir2/file2 && + test_tick && + git commit -m "populate tree" + +' + +test_expect_success 'remove and restore with relative path' ' + + ( + cd dir1 && + rm ../file0 && + git checkout HEAD -- ../file0 && + test "base" = "$(cat ../file0)" && + rm ../dir2/file2 && + git checkout HEAD -- ../dir2/file2 && + test "bonjour" = "$(cat ../dir2/file2)" && + rm ../file0 ./file1 && + git checkout HEAD -- .. && + test "base" = "$(cat ../file0)" && + test "hello" = "$(cat file1)" + ) + +' + +test_expect_success 'checkout with empty prefix' ' + + rm file0 && + git checkout HEAD -- file0 && + test "base" = "$(cat file0)" + +' + +test_expect_success 'checkout with simple prefix' ' + + rm dir1/file1 && + git checkout HEAD -- dir1 && + test "hello" = "$(cat dir1/file1)" && + rm dir1/file1 && + git checkout HEAD -- dir1/file1 && + test "hello" = "$(cat dir1/file1)" + +' + +# This is not expected to work as ls-files was not designed +# to deal with such. Enable it when ls-files is updated. +: test_expect_success 'checkout with complex relative path' ' + + rm file1 && + git checkout HEAD -- ../dir1/../dir1/file1 && test -f ./file1 + +' + +test_expect_failure 'relative path outside tree should fail' \ + 'git checkout HEAD -- ../../Makefile' + +test_expect_failure 'incorrect relative path to file should fail (1)' \ + 'git checkout HEAD -- ../file0' + +test_expect_failure 'incorrect relative path should fail (2)' \ + '( cd dir1 && git checkout HEAD -- ./file0 )' + +test_expect_failure 'incorrect relative path should fail (3)' \ + '( cd dir1 && git checkout HEAD -- ../../file0 )' + +test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 0779aaa9ab..7b7d07269a 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -48,9 +48,14 @@ test_expect_success 'reference merge' ' git merge -s recursive "reference merge" HEAD master ' +PRE_REBASE=$(git rev-parse test-rebase) test_expect_success rebase ' git checkout test-rebase && - git rebase --merge master + GIT_TRACE=1 git rebase --merge master +' + +test_expect_success 'test-rebase@{1} is pre rebase' ' + test $PRE_REBASE = $(git rev-parse test-rebase@{1}) ' test_expect_success 'merge and rebase should match' ' diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index eab053c3e0..657f68104d 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -36,21 +36,36 @@ test_expect_failure 'rebase with git am -3 (default)' ' ' test_expect_success 'rebase --skip with am -3' ' - git reset --hard HEAD && git rebase --skip ' + +test_expect_success 'rebase moves back to skip-reference' ' + test refs/heads/skip-reference = $(git symbolic-ref HEAD) && + git branch post-rebase && + git reset --hard pre-rebase && + ! git rebase master && + echo "hello" > hello && + git add hello && + git rebase --continue && + test refs/heads/skip-reference = $(git symbolic-ref HEAD) && + git reset --hard post-rebase +' + test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge' test_expect_failure 'rebase with --merge' 'git rebase --merge master' test_expect_success 'rebase --skip with --merge' ' - git reset --hard HEAD && git rebase --skip ' test_expect_success 'merge and reference trees equal' \ 'test -z "`git diff-tree skip-merge skip-reference`"' +test_expect_success 'moved back to branch correctly' ' + test refs/heads/skip-merge = $(git symbolic-ref HEAD) +' + test_debug 'gitk --all & sleep 1' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 984146b5c2..907c7f9f6b 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -80,7 +80,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash) + squash|edit) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" @@ -149,7 +149,8 @@ test_expect_success 'stop on conflicting pick' ' diff -u expect .git/.dotest-merge/patch && diff -u expect2 file1 && test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) && - test 0 = $(grep -v "^#" < .git/.dotest-merge/git-rebase-todo | wc -l) + test 0 = $(grep -ve "^#" -e "^$" < .git/.dotest-merge/git-rebase-todo | + wc -l) ' test_expect_success 'abort' ' @@ -297,4 +298,24 @@ test_expect_success 'ignore patch if in upstream' ' test $HEAD = $(git rev-parse HEAD^) ' +test_expect_success '--continue tries to commit, even for "edit"' ' + parent=$(git rev-parse HEAD^) && + test_tick && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo edited > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue && + test edited = $(git show HEAD:file7) && + git show HEAD | grep chouette && + test $parent = $(git rev-parse HEAD^) +' + +test_expect_success 'rebase a detached HEAD' ' + grandparent=$(git rev-parse HEAD~2) && + git checkout $(git rev-parse HEAD) && + test_tick && + FAKE_LINES="2 1" git rebase -i HEAD~2 && + test $grandparent = $(git rev-parse HEAD~2) +' + test_done diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh new file mode 100755 index 0000000000..7c92e261fc --- /dev/null +++ b/t/t3502-cherry-pick-merge.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +test_description='cherry picking and reverting a merge + + b---c + / / + initial---a + +' + +. ./test-lib.sh + +test_expect_success setup ' + + >A && + >B && + git add A B && + git commit -m "Initial" && + git tag initial && + git branch side && + echo new line >A && + git commit -m "add line to A" A && + git tag a && + git checkout side && + echo new line >B && + git commit -m "add line to B" B && + git tag b && + git checkout master && + git merge side && + git tag c + +' + +test_expect_success 'cherry-pick a non-merge with -m should fail' ' + + git reset --hard && + git checkout a^0 && + ! git cherry-pick -m 1 b && + git diff --exit-code a -- + +' + +test_expect_success 'cherry pick a merge without -m should fail' ' + + git reset --hard && + git checkout a^0 && + ! git cherry-pick c && + git diff --exit-code a -- + +' + +test_expect_success 'cherry pick a merge (1)' ' + + git reset --hard && + git checkout a^0 && + git cherry-pick -m 1 c && + git diff --exit-code c + +' + +test_expect_success 'cherry pick a merge (2)' ' + + git reset --hard && + git checkout b^0 && + git cherry-pick -m 2 c && + git diff --exit-code c + +' + +test_expect_success 'cherry pick a merge relative to nonexistent parent should fail' ' + + git reset --hard && + git checkout b^0 && + ! git cherry-pick -m 3 c + +' + +test_expect_success 'revert a non-merge with -m should fail' ' + + git reset --hard && + git checkout c^0 && + ! git revert -m 1 b && + git diff --exit-code c + +' + +test_expect_success 'revert a merge without -m should fail' ' + + git reset --hard && + git checkout c^0 && + ! git revert c && + git diff --exit-code c + +' + +test_expect_success 'revert a merge (1)' ' + + git reset --hard && + git checkout c^0 && + git revert -m 1 c && + git diff --exit-code a -- + +' + +test_expect_success 'revert a merge (2)' ' + + git reset --hard && + git checkout c^0 && + git revert -m 2 c && + git diff --exit-code b -- + +' + +test_expect_success 'revert a merge relative to nonexistent parent should fail' ' + + git reset --hard && + git checkout c^0 && + ! git revert -m 3 c && + git diff --exit-code c + +' + +test_done diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index 245fb3babd..73da45f18c 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -20,6 +20,13 @@ LF=' ' DQ='"' +echo foo > "Name and an${HT}HT" +test -f "Name and an${HT}HT" || { + # since FAT/NTFS does not allow tabs in filenames, skip this test + say 'Your filesystem does not allow tabs in filenames, test skipped.' + test_done +} + for_each_name () { for name in \ Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \ diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh new file mode 100755 index 0000000000..43d64bbd82 --- /dev/null +++ b/t/t4021-format-patch-numbered.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# +# Copyright (c) 2006 Brian C Gernhardt +# + +test_description='Format-patch numbering options' + +. ./test-lib.sh + +test_expect_success setup ' + + echo A > file && + git add file && + git commit -m First && + + echo B >> file && + git commit -a -m Second && + + echo C >> file && + git commit -a -m Third + +' + +# Each of these gets used multiple times. + +test_num_no_numbered() { + cnt=$(grep "^Subject: \[PATCH\]" $1 | wc -l) && + test $cnt = $2 +} + +test_single_no_numbered() { + test_num_no_numbered $1 1 +} + +test_no_numbered() { + test_num_no_numbered $1 2 +} + +test_single_numbered() { + grep "^Subject: \[PATCH 1/1\]" $1 +} + +test_numbered() { + grep "^Subject: \[PATCH 1/2\]" $1 && + grep "^Subject: \[PATCH 2/2\]" $1 +} + +test_expect_success 'Default: no numbered' ' + + git format-patch --stdout HEAD~2 >patch0 && + test_no_numbered patch0 + +' + +test_expect_success 'Use --numbered' ' + + git format-patch --numbered --stdout HEAD~2 >patch1 && + test_numbered patch1 + +' + +test_expect_success 'format.numbered = true' ' + + git config format.numbered true && + git format-patch --stdout HEAD~2 >patch2 && + test_numbered patch2 + +' + +test_expect_success 'format.numbered && single patch' ' + + git format-patch --stdout HEAD^ > patch3 && + test_single_numbered patch3 + +' + +test_expect_success 'format.numbered && --no-numbered' ' + + git format-patch --no-numbered --stdout HEAD~2 >patch4 && + test_no_numbered patch4 + +' + +test_expect_success 'format.numbered = auto' ' + + git config format.numbered auto + git format-patch --stdout HEAD~2 > patch5 && + test_numbered patch5 + +' + +test_expect_success 'format.numbered = auto && single patch' ' + + git format-patch --stdout HEAD^ > patch6 && + test_single_no_numbered patch6 + +' + +test_expect_success 'format.numbered = auto && --no-numbered' ' + + git format-patch --no-numbered --stdout HEAD~2 > patch7 && + test_no_numbered patch7 + +' + +test_done diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh new file mode 100755 index 0000000000..6de4acbd44 --- /dev/null +++ b/t/t4022-diff-rewrite.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='rewrite diff' + +. ./test-lib.sh + +test_expect_success setup ' + + cat ../../COPYING >test && + git add test && + tr 'a-zA-Z' 'n-za-mN-ZA-M' <../../COPYING >test + +' + +test_expect_success 'detect rewrite' ' + + actual=$(git diff-files -B --summary test) && + expr "$actual" : " rewrite test ([0-9]*%)$" || { + echo "Eh? <<$actual>>" + false + } + +' + +test_done + diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 65571e0549..b540f7295a 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -24,7 +24,7 @@ cat >gpatch.file <<\EOF && +++ file1+ 2007-02-21 01:07:44.000000000 -0800 @@ -1 +1 @@ -A -+B ++B EOF sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file && diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 1a4c53a031..dca2067b2d 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -28,12 +28,16 @@ commit id embedding: TAR=${TAR:-tar} UNZIP=${UNZIP:-unzip} +SUBSTFORMAT=%H%n + test_expect_success \ 'populate workdir' \ 'mkdir a b c && echo simple textfile >a/a && mkdir a/bin && cp /bin/sh a/bin && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && + printf "A not substituted O" >a/substfile2 && ln -s a a/l1 && (p=long_path_to_a_file && cd a && for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && @@ -105,6 +109,24 @@ test_expect_success \ 'diff -r a c/prefix/a' test_expect_success \ + 'create an archive with a substfiles' \ + 'echo "substfile?" export-subst >a/.gitattributes && + git archive HEAD >f.tar && + rm a/.gitattributes' + +test_expect_success \ + 'extract substfiles' \ + '(mkdir f && cd f && $TAR xf -) <f.tar' + +test_expect_success \ + 'validate substfile contents' \ + 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ + >f/a/substfile1.expected && + diff f/a/substfile1.expected f/a/substfile1 && + diff a/substfile2 f/a/substfile2 +' + +test_expect_success \ 'git archive --format=zip' \ 'git archive --format=zip HEAD >d.zip' diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index b80c981c16..070c1661b9 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -1,3 +1,6 @@ + + + From nobody Mon Sep 17 00:00:00 2001 From: A U Thor <a.u.thor@example.com> Date: Fri, 9 Jun 2006 00:44:16 -0700 diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index ba7579c251..f1106e6542 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -187,49 +187,51 @@ test_expect_success \ test-3-${packname_3}.idx' test_expect_success \ - 'corrupt a pack and see if verify catches' \ + 'verify-pack catches mismatched .idx and .pack files' \ 'cat test-1-${packname_1}.idx >test-3.idx && cat test-2-${packname_2}.pack >test-3.pack && if git verify-pack test-3.idx then false else :; - fi && + fi' - : PACK_SIGNATURE && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted pack signature' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : PACK_VERSION && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted pack version' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : TYPE/SIZE byte of the first packed object data && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted type/size of the 1st packed object data' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : sum of the index file itself && - l=`wc -c <test-3.idx` && +test_expect_success \ + 'verify-pack catches a corrupted sum of the index file itself' \ + 'l=`wc -c <test-3.idx` && l=`expr $l - 20` && cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l && if git verify-pack test-3.pack then false else :; - fi && - - :' + fi' test_expect_success \ 'build pack index for an existing pack' \ diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 4f58c4c3f9..2a2878b572 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -61,17 +61,33 @@ test_expect_success \ test_expect_success \ 'index v2: force some 64-bit offsets with pack-objects' \ - 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list) && - git verify-pack -v "test-3-${pack3}.pack"' + 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)' + +have_64bits= +if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) || + ! echo "$msg" | grep "pack too large .* off_t" +then + have_64bits=t +else + say "skipping tests concerning 64-bit offsets" +fi + +test "$have_64bits" && +test_expect_success \ + 'index v2: verify a pack with some 64-bit offsets' \ + 'git verify-pack -v "test-3-${pack3}.pack"' +test "$have_64bits" && test_expect_failure \ '64-bit offsets: should be different from previous index v2 results' \ 'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' +test "$have_64bits" && test_expect_success \ 'index v2: force some 64-bit offsets with index-pack' \ 'git-index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"' +test "$have_64bits" && test_expect_success \ '64-bit offsets: index-pack result should match pack-objects one' \ 'cmp "test-3-${pack3}.idx" "3.idx"' @@ -116,11 +132,11 @@ test_expect_failure \ test_expect_success \ '[index v2] 1) stream pack to repository' \ 'rm -f .git/objects/pack/* && - git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" && + git-index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && git prune-packed && git count-objects | ( read nr rest && test "$nr" -eq 1 ) && cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && - cmp "test-3-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' + cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' test_expect_success \ '[index v2] 2) create a stealth corruption in a delta base reference' \ diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh new file mode 100755 index 0000000000..1c4b0b32ab --- /dev/null +++ b/t/t5402-post-merge-hook.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Copyright (c) 2006 Josh England +# + +test_description='Test the post-merge hook.' +. ./test-lib.sh + +test_expect_success setup ' + echo Data for commit0. >a && + git update-index --add a && + tree0=$(git write-tree) && + commit0=$(echo setup | git commit-tree $tree0) && + echo Changed data for commit1. >a && + git update-index a && + tree1=$(git write-tree) && + commit1=$(echo modify | git commit-tree $tree1 -p $commit0) && + git update-ref refs/heads/master $commit0 && + git-clone ./. clone1 && + GIT_DIR=clone1/.git git update-index --add a && + git-clone ./. clone2 && + GIT_DIR=clone2/.git git update-index --add a +' + +for clone in 1 2; do + cat >clone${clone}/.git/hooks/post-merge <<'EOF' +#!/bin/sh +echo $@ >> $GIT_DIR/post-merge.args +EOF + chmod u+x clone${clone}/.git/hooks/post-merge +done + +test_expect_failure 'post-merge does not run for up-to-date ' ' + GIT_DIR=clone1/.git git merge $commit0 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge runs as expected ' ' + GIT_DIR=clone1/.git git merge $commit1 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from normal merge receives the right argument ' ' + grep 0 clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge runs as expected ' ' + GIT_DIR=clone2/.git git merge --squash $commit1 && + test -e clone2/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge receives the right argument ' ' + grep 1 clone2/.git/post-merge.args +' + +test_done diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh new file mode 100755 index 0000000000..823239a251 --- /dev/null +++ b/t/t5403-post-checkout-hook.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Copyright (c) 2006 Josh England +# + +test_description='Test the post-checkout hook.' +. ./test-lib.sh + +test_expect_success setup ' + echo Data for commit0. >a && + echo Data for commit0. >b && + git update-index --add a && + git update-index --add b && + tree0=$(git write-tree) && + commit0=$(echo setup | git commit-tree $tree0) && + git update-ref refs/heads/master $commit0 && + git-clone ./. clone1 && + git-clone ./. clone2 && + GIT_DIR=clone2/.git git branch -a new2 && + echo Data for commit1. >clone2/b && + GIT_DIR=clone2/.git git add clone2/b && + GIT_DIR=clone2/.git git commit -m new2 +' + +for clone in 1 2; do + cat >clone${clone}/.git/hooks/post-checkout <<'EOF' +#!/bin/sh +echo $@ > $GIT_DIR/post-checkout.args +EOF + chmod u+x clone${clone}/.git/hooks/post-checkout +done + +test_expect_success 'post-checkout runs as expected ' ' + GIT_DIR=clone1/.git git checkout master && + test -e clone1/.git/post-checkout.args +' + +test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' ' + old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test $old = $new -a $flag = 1 +' + +test_expect_success 'post-checkout runs as expected ' ' + GIT_DIR=clone1/.git git checkout master && + test -e clone1/.git/post-checkout.args +' + +test_expect_success 'post-checkout args are correct with git checkout -b ' ' + GIT_DIR=clone1/.git git checkout -b new1 && + old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test $old = $new -a $flag = 1 +' + +test_expect_success 'post-checkout receives the right args with HEAD changed ' ' + GIT_DIR=clone2/.git git checkout new2 && + old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test $old != $new -a $flag = 1 +' + +test_expect_success 'post-checkout receives the right args when not switching branches ' ' + GIT_DIR=clone2/.git git checkout master b && + old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test $old = $new -a $flag = 0 +' + +test_done diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh new file mode 100755 index 0000000000..1493a92c06 --- /dev/null +++ b/t/t5404-tracking-branches.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description='tracking branch update checks for git push' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >file && + git add file && + git commit -m 1 && + git branch b1 && + git branch b2 && + git clone . aa && + git checkout b1 && + echo b1 >>file && + git commit -a -m b1 && + git checkout b2 && + echo b2 >>file && + git commit -a -m b2 +' + +test_expect_success 'prepare pushable branches' ' + cd aa && + b1=$(git rev-parse origin/b1) && + b2=$(git rev-parse origin/b2) && + git checkout -b b1 origin/b1 && + echo aa-b1 >>file && + git commit -a -m aa-b1 && + git checkout -b b2 origin/b2 && + echo aa-b2 >>file && + git commit -a -m aa-b2 && + git checkout master && + echo aa-master >>file && + git commit -a -m aa-master +' + +test_expect_success 'mixed-success push returns error' '! git push' + +test_expect_success 'check tracking branches updated correctly after push' ' + test "$(git rev-parse origin/master)" = "$(git rev-parse master)" +' + +test_expect_success 'check tracking branches not updated for failed refs' ' + test "$(git rev-parse origin/b1)" = "$b1" && + test "$(git rev-parse origin/b2)" = "$b2" +' + +test_expect_success 'deleted branches have their tracking branches removed' ' + git push origin :b1 && + test "$(git rev-parse origin/b1)" = "origin/b1" +' + +test_done diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh new file mode 100755 index 0000000000..86abc62271 --- /dev/null +++ b/t/t5405-send-pack-rewind.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='forced push to replace commit we do not have' + +. ./test-lib.sh + +test_expect_success setup ' + + >file1 && git add file1 && test_tick && + git commit -m Initial && + + mkdir another && ( + cd another && + git init && + git fetch .. master:master + ) && + + >file2 && git add file2 && test_tick && + git commit -m Second + +' + +test_expect_success 'non forced push should die not segfault' ' + + ( + cd another && + git push .. master:master + test $? = 1 + ) + +' + +test_expect_success 'forced push should succeed' ' + + ( + cd another && + git push .. +master:master + ) + +' + +test_done diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh new file mode 100755 index 0000000000..46b2cb4e46 --- /dev/null +++ b/t/t5406-remote-rejects.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='remote push rejects are reported by client' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir .git/hooks && + (echo "#!/bin/sh" ; echo "exit 1") >.git/hooks/update && + chmod +x .git/hooks/update && + echo 1 >file && + git add file && + git commit -m 1 && + git clone . child && + cd child && + echo 2 >file && + git commit -a -m 2 +' + +test_expect_success 'push reports error' '! git push 2>stderr' + +test_expect_success 'individual ref reports error' 'grep rejected stderr' + +test_done diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh index b4760f2dc0..16eadd6b68 100755 --- a/t/t5502-quickfetch.sh +++ b/t/t5502-quickfetch.sh @@ -86,4 +86,37 @@ test_expect_success 'quickfetch should not leave a corrupted repository' ' ' +test_expect_success 'quickfetch should not copy from alternate' ' + + ( + mkdir quickclone && + cd quickclone && + git init-db && + (cd ../.git/objects && pwd) >.git/objects/info/alternates && + git remote add origin .. && + git fetch -k -k + ) && + obj_cnt=$( ( + cd quickclone && + git count-objects | sed -e "s/ *objects,.*//" + ) ) && + pck_cnt=$( ( + cd quickclone && + git count-objects -v | sed -n -e "/packs:/{ + s/packs:// + p + q + }" + ) ) && + origin_master=$( ( + cd quickclone && + git rev-parse origin/master + ) ) && + echo "loose objects: $obj_cnt, packfiles: $pck_cnt" && + test $obj_cnt -eq 0 && + test $pck_cnt -eq 0 && + test z$origin_master = z$(git rev-parse master) + +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh new file mode 100755 index 0000000000..636aec2f71 --- /dev/null +++ b/t/t5505-remote.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +test_description='git remote porcelain-ish' + +. ./test-lib.sh + +GIT_CONFIG=.git/config +export GIT_CONFIG + +setup_repository () { + mkdir "$1" && ( + cd "$1" && + git init && + >file && + git add file && + git commit -m "Initial" && + git checkout -b side && + >elif && + git add elif && + git commit -m "Second" && + git checkout master + ) +} + +tokens_match () { + echo "$1" | tr ' ' '\012' | sort | sed -e '/^$/d' >expect && + echo "$2" | tr ' ' '\012' | sort | sed -e '/^$/d' >actual && + diff -u expect actual +} + +check_remote_track () { + actual=$(git remote show "$1" | sed -n -e '$p') && + shift && + tokens_match "$*" "$actual" +} + +check_tracking_branch () { + f="" && + r=$(git for-each-ref "--format=%(refname)" | + sed -ne "s|^refs/remotes/$1/||p") && + shift && + tokens_match "$*" "$r" +} + +test_expect_success setup ' + + setup_repository one && + setup_repository two && + ( + cd two && git branch another + ) && + git clone one test + +' + +test_expect_success 'remote information for the origin' ' +( + cd test && + tokens_match origin "$(git remote)" && + check_remote_track origin master side && + check_tracking_branch origin HEAD master side +) +' + +test_expect_success 'add another remote' ' +( + cd test && + git remote add -f second ../two && + tokens_match "origin second" "$(git remote)" && + check_remote_track origin master side && + check_remote_track second master side another && + check_tracking_branch second master side another && + git for-each-ref "--format=%(refname)" refs/remotes | + sed -e "/^refs\/remotes\/origin\//d" \ + -e "/^refs\/remotes\/second\//d" >actual && + >expect && + diff -u expect actual +) +' + +test_expect_success 'remove remote' ' +( + cd test && + git remote rm second +) +' + +test_expect_success 'remove remote' ' +( + cd test && + tokens_match origin "$(git remote)" && + check_remote_track origin master side && + git for-each-ref "--format=%(refname)" refs/remotes | + sed -e "/^refs\/remotes\/origin\//d" >actual && + >expect && + diff -u expect actual +) +' + +test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 7406de35ae..35889c0a12 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -67,6 +67,18 @@ test_expect_success "fetch test for-merge" ' cut -f -2 .git/FETCH_HEAD >actual && diff expected actual' +test_expect_success 'fetch tags when there is no tags' ' + + cd "$D" && + + mkdir notags && + cd notags && + git init && + + git fetch -t .. + +' + test_expect_success 'fetch following tags' ' cd "$D" && @@ -153,6 +165,56 @@ test_expect_success 'bundle should be able to create a full history' ' ' +test "$TEST_RSYNC" && { +test_expect_success 'fetch via rsync' ' + git pack-refs && + mkdir rsynced && + cd rsynced && + git init && + git fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master && + git gc --prune && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full +' + +test_expect_success 'push via rsync' ' + mkdir ../rsynced2 && + (cd ../rsynced2 && + git init) && + git push rsync://127.0.0.1$(pwd)/../rsynced2/.git master && + cd ../rsynced2 && + git gc --prune && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full +' + +test_expect_success 'push via rsync' ' + cd .. && + mkdir rsynced3 && + (cd rsynced3 && + git init) && + git push --all rsync://127.0.0.1$(pwd)/rsynced3/.git && + cd rsynced3 && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full +' +} + +test_expect_success 'fetch with a non-applying branch.<name>.merge' ' + git config branch.master.remote yeti && + git config branch.master.merge refs/heads/bigfoot && + git config remote.blub.url one && + git config remote.blub.fetch "refs/heads/*:refs/remotes/one/*" && + git fetch blub +' + +# the strange name is: a\!'b +test_expect_success 'quoting of a strangely named repo' ' + ! git fetch "a\\!'\''b" > result 2>&1 && + cat result && + grep "fatal: '\''a\\\\!'\''b'\''" result +' + test_expect_success 'bundle should record HEAD correctly' ' cd "$D" && diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh new file mode 100755 index 0000000000..6ec5f7c48b --- /dev/null +++ b/t/t5512-ls-remote.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='git ls-remote' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + test_tick && + git commit -m initial && + git tag mark && + git show-ref --tags -d | sed -e "s/ / /" >expected.tag && + ( + echo "$(git rev-parse HEAD) HEAD" + git show-ref -d | sed -e "s/ / /" + ) >expected.all && + + git remote add self $(pwd)/.git + +' + +test_expect_success 'ls-remote --tags .git' ' + + git ls-remote --tags .git >actual && + diff -u expected.tag actual + +' + +test_expect_success 'ls-remote .git' ' + + git ls-remote .git >actual && + diff -u expected.all actual + +' + +test_expect_success 'ls-remote --tags self' ' + + git ls-remote --tags self >actual && + diff -u expected.tag actual + +' + +test_expect_success 'ls-remote self' ' + + git ls-remote self >actual && + diff -u expected.all actual + +' + +test_done diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh index 6c9cc67508..31c1081617 100755 --- a/t/t5515-fetch-merge-logic.sh +++ b/t/t5515-fetch-merge-logic.sh @@ -84,8 +84,7 @@ test_expect_success setup ' git config branch.br-$remote-merge.merge refs/heads/three && git config branch.br-$remote-octopus.remote $remote && git config branch.br-$remote-octopus.merge refs/heads/one && - git config --add branch.br-$remote-octopus.merge two && - git config --add branch.br-$remote-octopus.merge remotes/rem/three + git config --add branch.br-$remote-octopus.merge two done ' diff --git a/t/t5515/fetch.br-branches-default-merge b/t/t5515/fetch.br-branches-default-merge index ea65f31bde..ca2cc1d1b4 100644 --- a/t/t5515/fetch.br-branches-default-merge +++ b/t/t5515/fetch.br-branches-default-merge @@ -1,5 +1,6 @@ # br-branches-default-merge -754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ +754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ +0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-default-merge_branches-default b/t/t5515/fetch.br-branches-default-merge_branches-default index 7b5fa949e6..7d947cd80f 100644 --- a/t/t5515/fetch.br-branches-default-merge_branches-default +++ b/t/t5515/fetch.br-branches-default-merge_branches-default @@ -1,5 +1,6 @@ # br-branches-default-merge branches-default -754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ +754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ +0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-default-octopus b/t/t5515/fetch.br-branches-default-octopus index 128397d737..ec39c54b7e 100644 --- a/t/t5515/fetch.br-branches-default-octopus +++ b/t/t5515/fetch.br-branches-default-octopus @@ -1,5 +1,7 @@ # br-branches-default-octopus -754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ +754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ +8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-default-octopus_branches-default b/t/t5515/fetch.br-branches-default-octopus_branches-default index 4b37cd481a..6bf42e24b6 100644 --- a/t/t5515/fetch.br-branches-default-octopus_branches-default +++ b/t/t5515/fetch.br-branches-default-octopus_branches-default @@ -1,5 +1,7 @@ # br-branches-default-octopus branches-default -754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ +754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ +8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-one-merge b/t/t5515/fetch.br-branches-one-merge index 3a4e77ead5..b4b3b35ce0 100644 --- a/t/t5515/fetch.br-branches-one-merge +++ b/t/t5515/fetch.br-branches-one-merge @@ -1,5 +1,6 @@ # br-branches-one-merge -8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ +0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-one-merge_branches-one b/t/t5515/fetch.br-branches-one-merge_branches-one index 00e04b435e..2ecef384eb 100644 --- a/t/t5515/fetch.br-branches-one-merge_branches-one +++ b/t/t5515/fetch.br-branches-one-merge_branches-one @@ -1,5 +1,6 @@ # br-branches-one-merge branches-one -8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ +0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-one-octopus b/t/t5515/fetch.br-branches-one-octopus index 53fe808a3b..96e3029416 100644 --- a/t/t5515/fetch.br-branches-one-octopus +++ b/t/t5515/fetch.br-branches-one-octopus @@ -1,5 +1,6 @@ # br-branches-one-octopus 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-branches-one-octopus_branches-one b/t/t5515/fetch.br-branches-one-octopus_branches-one index 41b18ff78a..55e0bad621 100644 --- a/t/t5515/fetch.br-branches-one-octopus_branches-one +++ b/t/t5515/fetch.br-branches-one-octopus_branches-one @@ -1,5 +1,6 @@ # br-branches-one-octopus branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-config-glob-octopus b/t/t5515/fetch.br-config-glob-octopus index 9ee213ea45..938e532db2 100644 --- a/t/t5515/fetch.br-config-glob-octopus +++ b/t/t5515/fetch.br-config-glob-octopus @@ -2,7 +2,7 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-config-glob-octopus_config-glob b/t/t5515/fetch.br-config-glob-octopus_config-glob index 44bd0ec59f..c9225bf6ff 100644 --- a/t/t5515/fetch.br-config-glob-octopus_config-glob +++ b/t/t5515/fetch.br-config-glob-octopus_config-glob @@ -2,7 +2,7 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-remote-glob-octopus b/t/t5515/fetch.br-remote-glob-octopus index c1554f8f2d..b08e046195 100644 --- a/t/t5515/fetch.br-remote-glob-octopus +++ b/t/t5515/fetch.br-remote-glob-octopus @@ -2,7 +2,7 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5515/fetch.br-remote-glob-octopus_remote-glob b/t/t5515/fetch.br-remote-glob-octopus_remote-glob index e6134345b8..d4d547c847 100644 --- a/t/t5515/fetch.br-remote-glob-octopus_remote-glob +++ b/t/t5515/fetch.br-remote-glob-octopus_remote-glob @@ -2,7 +2,7 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ +6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index ca46aafe72..4fbd5b1f47 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -244,4 +244,14 @@ test_expect_success 'push with colon-less refspec (4)' ' ' +test_expect_success 'push with dry-run' ' + + mk_test heads/master && + cd testrepo && + old_commit=$(git show-ref -s --verify refs/heads/master) && + cd .. && + git push --dry-run testrepo && + check_push_result $old_commit heads/master +' + test_done diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh new file mode 100755 index 0000000000..ed3fec192a --- /dev/null +++ b/t/t5517-push-mirror.sh @@ -0,0 +1,228 @@ +#!/bin/sh + +test_description='pushing to a mirror repository' + +. ./test-lib.sh + +D=`pwd` + +invert () { + if "$@"; then + return 1 + else + return 0 + fi +} + +mk_repo_pair () { + rm -rf master mirror && + mkdir mirror && + ( + cd mirror && + git init + ) && + mkdir master && + ( + cd master && + git init && + git config remote.up.url ../mirror + ) +} + + +# BRANCH tests +test_expect_success 'push mirror creates new branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up && + git reset --hard HEAD^ + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + +test_expect_success 'push mirror adds, updates and removes branches together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove && + git branch add master && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + master_add=$(cd master && git show-ref -s --verify refs/heads/add) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + + +# TAG tests +test_expect_success 'push mirror creates new tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up && + git reset --hard HEAD^ + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_expect_success 'push mirror adds, updates and removes tags together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove && + git tag tadd master && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_done diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh new file mode 100755 index 0000000000..cc8949e3ef --- /dev/null +++ b/t/t5530-upload-pack-error.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +test_description='errors in upload-pack' + +. ./test-lib.sh + +D=`pwd` + +corrupt_repo () { + object_sha1=$(git rev-parse "$1") && + ob=$(expr "$object_sha1" : "\(..\)") && + ject=$(expr "$object_sha1" : "..\(..*\)") && + rm -f ".git/objects/$ob/$ject" +} + +test_expect_success 'setup and corrupt repository' ' + + echo file >file && + git add file && + git rev-parse :file && + git commit -a -m original && + test_tick && + echo changed >file && + git commit -a -m changed && + corrupt_repo HEAD:file + +' + +test_expect_failure 'fsck fails' ' + + git fsck +' + +test_expect_success 'upload-pack fails due to error in pack-objects' ' + + ! echo "0032want $(git rev-parse HEAD) +00000009done +0000" | git-upload-pack . > /dev/null 2> output.err && + grep "pack-objects died" output.err +' + +test_expect_success 'corrupt repo differently' ' + + git hash-object -w file && + corrupt_repo HEAD^^{tree} + +' + +test_expect_failure 'fsck fails' ' + + git fsck +' +test_expect_success 'upload-pack fails due to error in rev-list' ' + + ! echo "0032want $(git rev-parse HEAD) +00000009done +0000" | git-upload-pack . > /dev/null 2> output.err && + grep "waitpid (async) failed" output.err +' + +test_expect_success 'create empty repository' ' + + mkdir foo && + cd foo && + git init + +' + +test_expect_failure 'fetch fails' ' + + git fetch .. master + +' + +test_done diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 4e93aaab02..b6a54867b4 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -38,7 +38,7 @@ cd "$base_dir" test_expect_success 'pulling from reference' \ 'cd C && -git pull ../B' +git pull ../B master' cd "$base_dir" @@ -61,7 +61,7 @@ test_expect_success 'existence of info/alternates' \ cd "$base_dir" test_expect_success 'pulling from reference' \ -'cd D && git pull ../B' +'cd D && git pull ../B master' cd "$base_dir" diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index ad6d0b8c9d..1e4541afea 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -79,9 +79,7 @@ EOF test_format encoding %e <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d -<unknown> commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -<unknown> EOF test_format subject %s <<'EOF' @@ -93,9 +91,7 @@ EOF test_format body %b <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d -<unknown> commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -<unknown> EOF test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF' @@ -121,9 +117,7 @@ test_format complex-encoding %e <<'EOF' commit f58db70b055c5718631e5c61528b28b12090cdea iso8859-1 commit 131a310eb913d107dd3c09a65d1651175898735d -<unknown> commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -<unknown> EOF test_format complex-subject %s <<'EOF' @@ -142,9 +136,7 @@ and it will be encoded in iso8859-1. We should therefore include an iso8859 character: ¡bueno! commit 131a310eb913d107dd3c09a65d1651175898735d -<unknown> commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -<unknown> EOF test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 03cdba5808..2ba4b00e52 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -71,6 +71,100 @@ test_expect_success 'bisect start with one bad and good' ' git bisect next ' +test_expect_success 'bisect reset: back in the master branch' ' + git bisect reset && + echo "* master" > branch.expect && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset: back in another branch' ' + git checkout -b other && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH3 && + git bisect reset && + echo " master" > branch.expect && + echo "* other" >> branch.expect && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset when not bisecting' ' + git bisect reset && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset removes packed refs' ' + git bisect reset && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH3 && + git pack-refs --all --prune && + git bisect next && + git bisect reset && + test -z "$(git for-each-ref "refs/bisect/*")" && + test -z "$(git for-each-ref "refs/heads/bisect")" +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is bad, +# so we should find $HASH2 as the first bad commit +test_expect_success 'bisect skip: successfull result' ' + git bisect reset && + git bisect start $HASH4 $HASH1 && + git bisect skip && + git bisect bad > my_bisect_log.txt && + grep "$HASH2 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2 +# so we should not be able to tell the first bad commit +# among $HASH2, $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 3 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect skip > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is good, +# so we should not be able to tell the first bad commit +# among $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 2 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect good > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + ! grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + # We want to automatically find the commit that # introduced "Another" into hello. test_expect_success \ @@ -99,6 +193,67 @@ test_expect_success \ grep "$HASH4 is first bad commit" my_bisect_log.txt && git bisect reset' +# $HASH1 is good, $HASH5 is bad, we skip $HASH3 +# but $HASH4 is good, +# so we should find $HASH5 as the first bad commit +HASH5= +test_expect_success 'bisect skip: add line and then a new test' ' + add_line_into_file "5: Another new line." hello && + HASH5=$(git rev-parse --verify HEAD) && + git bisect start $HASH5 $HASH1 && + git bisect skip && + git bisect good > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect log > log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect skip and bisect replay' ' + git bisect replay log_to_replay.txt > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +HASH6= +test_expect_success 'bisect run & skip: cannot tell between 2' ' + add_line_into_file "6: Yet a line." hello && + HASH6=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "grep line hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH6 $HASH1 && + if git bisect run ./test_script.sh > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH3 my_bisect_log.txt && + ! grep $HASH6 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + grep $HASH5 my_bisect_log.txt + fi +' + +HASH7= +test_expect_success 'bisect run & skip: find first bad' ' + git bisect reset && + add_line_into_file "7: Should be the last line." hello && + HASH7=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh && + echo "grep Yet hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH7 $HASH1 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH6 is first bad commit" my_bisect_log.txt +' + # # test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh new file mode 100755 index 0000000000..c722635050 --- /dev/null +++ b/t/t6300-for-each-ref.sh @@ -0,0 +1,173 @@ +#!/bin/sh +# +# Copyright (c) 2007 Andy Parkins +# + +test_description='for-each-ref test' + +. ./test-lib.sh + +# Mon Jul 3 15:18:43 2006 +0000 +datestamp=1151939923 +setdate_and_increment () { + GIT_COMMITTER_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + GIT_AUTHOR_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +} + +test_expect_success 'Create sample commit with known timestamp' ' + setdate_and_increment && + echo "Using $datestamp" > one && + git add one && + git commit -m "Initial" && + setdate_and_increment && + git tag -a -m "Tagging at $datestamp" testtag +' + +test_expect_success 'Check atom names are valid' ' + bad= + for token in \ + refname objecttype objectsize objectname tree parent \ + numparent object type author authorname authoremail \ + authordate committer committername committeremail \ + committerdate tag tagger taggername taggeremail \ + taggerdate creator creatordate subject body contents + do + git for-each-ref --format="$token=%($token)" refs/heads || { + bad=$token + break + } + done + test -z "$bad" +' + +test_expect_failure 'Check invalid atoms names are errors' ' + git-for-each-ref --format="%(INVALID)" refs/heads +' + +test_expect_success 'Check format specifiers are ignored in naming date atoms' ' + git-for-each-ref --format="%(authordate)" refs/heads && + git-for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && + git-for-each-ref --format="%(authordate) %(authordate:default)" refs/heads && + git-for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads +' + +test_expect_success 'Check valid format specifiers for date fields' ' + git-for-each-ref --format="%(authordate:default)" refs/heads && + git-for-each-ref --format="%(authordate:relative)" refs/heads && + git-for-each-ref --format="%(authordate:short)" refs/heads && + git-for-each-ref --format="%(authordate:local)" refs/heads && + git-for-each-ref --format="%(authordate:iso8601)" refs/heads && + git-for-each-ref --format="%(authordate:rfc2822)" refs/heads +' + +test_expect_failure 'Check invalid format specifiers are errors' ' + git-for-each-ref --format="%(authordate:INVALID)" refs/heads +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200' +'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200' +EOF + +test_expect_success 'Check unformatted date fields output' ' + (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual && + git diff expected actual +' + +test_expect_success 'Check format "default" formatted date fields output' ' + f=default && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +# Don't know how to do relative check because I can't know when this script +# is going to be run and can't fake the current time to git, and hence can't +# provide expected output. Instead, I'll just make sure that "relative" +# doesn't exit in error +# +#cat >expected <<\EOF +# +#EOF +# +test_expect_success 'Check format "relative" date fields output' ' + f=relative && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual +' + +cat >expected <<\EOF +'refs/heads/master' '2006-07-03' '2006-07-03' +'refs/tags/testtag' '2006-07-03' +EOF + +test_expect_success 'Check format "short" date fields output' ' + f=short && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006' +'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006' +EOF + +test_expect_success 'Check format "local" date fields output' ' + f=local && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200' +'refs/tags/testtag' '2006-07-03 17:18:45 +0200' +EOF + +test_expect_success 'Check format "iso8601" date fields output' ' + f=iso8601 && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200' +'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200' +EOF + +test_expect_success 'Check format "rfc2822" date fields output' ' + f=rfc2822 && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +refs/heads/master +refs/tags/testtag +EOF + +test_expect_success 'Verify ascending sort' ' + git-for-each-ref --format="%(refname)" --sort=refname >actual && + git diff expected actual +' + + +cat >expected <<\EOF +refs/tags/testtag +refs/heads/master +EOF + +test_expect_success 'Verify descending sort' ' + git-for-each-ref --format="%(refname)" --sort=-refname >actual && + git diff expected actual +' + + +test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 0d07bc39c7..5f7e388d7a 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1004,4 +1004,30 @@ test_expect_failure \ 'verify signed tag fails when public key is not present' \ 'git-tag -v signed-tag' +test_expect_failure \ + 'git-tag -a fails if tag annotation is empty' ' + GIT_EDITOR=cat git tag -a initial-comment +' + +test_expect_success \ + 'message in editor has initial comment' ' + GIT_EDITOR=cat git tag -a initial-comment > actual + # check the first line --- should be empty + first=$(sed -e 1q <actual) && + test -z "$first" && + # remove commented lines from the remainder -- should be empty + rest=$(sed -e 1d -e '/^#/d' <actual) && + test -z "$rest" +' + +get_tag_header reuse $commit commit $time >expect +echo "An annotation to be reused" >> expect +test_expect_success \ + 'overwriting an annoted tag should use its previous body' ' + git tag -a -m "An annotation to be reused" reuse && + GIT_EDITOR=true git tag -f -a reuse && + get_tag_msg reuse >actual && + git diff expect actual +' + test_done diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index ed416e14e0..44228b5ac1 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -4,6 +4,8 @@ test_description='GIT_EDITOR, core.editor, and stuff' . ./test-lib.sh +OLD_TERM="$TERM" + for i in GIT_EDITOR core_editor EDITOR VISUAL vi do cat >e-$i.sh <<-EOF @@ -87,4 +89,6 @@ do ' done +TERM="$OLD_TERM" + test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh new file mode 100755 index 0000000000..e5c9f30c73 --- /dev/null +++ b/t/t7102-reset.sh @@ -0,0 +1,431 @@ +#!/bin/sh +# +# Copyright (c) 2007 Carlos Rica +# + +test_description='git-reset + +Documented tests for git-reset' + +. ./test-lib.sh + +test_expect_success 'creating initial files and commits' ' + test_tick && + echo "1st file" >first && + git add first && + git commit -m "create 1st file" && + + echo "2nd file" >second && + git add second && + git commit -m "create 2nd file" && + + echo "2nd line 1st file" >>first && + git commit -a -m "modify 1st file" && + + git rm first && + git mv second secondfile && + git commit -a -m "remove 1st and rename 2nd" && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git commit -a -m "modify 2nd file" +' +# git log --pretty=oneline # to see those SHA1 involved + +check_changes () { + test "$(git rev-parse HEAD)" = "$1" && + git diff | git diff .diff_expect - && + git diff --cached | git diff .cached_expect - && + for FILE in * + do + echo $FILE':' + cat $FILE || return + done | git diff .cat_expect - +} + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF + +test_expect_success 'giving a non existing revision should fail' ' + ! git reset aaaaaa && + ! git reset --mixed aaaaaa && + ! git reset --soft aaaaaa && + ! git reset --hard aaaaaa && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success 'reset --soft with unmerged index should fail' ' + touch .git/MERGE_HEAD && + echo "100644 44c5b5884550c17758737edcced463447b91d42b 1 un" | + git update-index --index-info && + ! git reset --soft HEAD && + rm .git/MERGE_HEAD && + git rm --cached -- un +' + +test_expect_success \ + 'giving paths with options different than --mixed should fail' ' + ! git reset --soft -- first && + ! git reset --hard -- first && + ! git reset --soft HEAD^ -- first && + ! git reset --hard HEAD^ -- first && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success 'giving unrecognized options should fail' ' + ! git reset --other && + ! git reset -o && + ! git reset --mixed --other && + ! git reset --mixed -o && + ! git reset --soft --other && + ! git reset --soft -o && + ! git reset --hard --other && + ! git reset --hard -o && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'trying to do reset --soft with pending merge should fail' ' + git branch branch1 && + git branch branch2 && + + git checkout branch1 && + echo "3rd line in branch1" >>secondfile && + git commit -a -m "change in branch1" && + + git checkout branch2 && + echo "3rd line in branch2" >>secondfile && + git commit -a -m "change in branch2" && + + ! git merge branch1 && + ! git reset --soft && + + printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile && + git commit -a -m "the change in branch2" && + + git checkout master && + git branch -D branch1 branch2 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'trying to do reset --soft with pending checkout merge should fail' ' + git branch branch3 && + git branch branch4 && + + git checkout branch3 && + echo "3rd line in branch3" >>secondfile && + git commit -a -m "line in branch3" && + + git checkout branch4 && + echo "3rd line in branch4" >>secondfile && + + git checkout -m branch3 && + ! git reset --soft && + + printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile && + git commit -a -m "the line in branch3" && + + git checkout master && + git branch -D branch3 branch4 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'resetting to HEAD with no changes should succeed and do nothing' ' + git reset --hard && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --hard HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --soft && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --soft HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --mixed && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --mixed HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +cat >.cached_expect <<EOF +diff --git a/secondfile b/secondfile +index 1bbba79..44c5b58 100644 +--- a/secondfile ++++ b/secondfile +@@ -1 +1,2 @@ +-2nd file ++1st line 2nd file ++2nd line 2nd file +EOF +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success '--soft reset only should show changes in diff --cached' ' + git reset --soft HEAD^ && + check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 && + test "$(git rev-parse ORIG_HEAD)" = \ + 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +3rd line 2nd file +EOF +test_expect_success \ + 'changing files and redo the last commit should succeed' ' + echo "3rd line 2nd file" >>secondfile && + git commit -a -C ORIG_HEAD && + check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d && + test "$(git rev-parse ORIG_HEAD)" = \ + 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +first: +1st file +2nd line 1st file +second: +2nd file +EOF +test_expect_success \ + '--hard reset should change the files and undo commits permanently' ' + git reset --hard HEAD~2 && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + test "$(git rev-parse ORIG_HEAD)" = \ + 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d +' + +>.diff_expect +cat >.cached_expect <<EOF +diff --git a/first b/first +deleted file mode 100644 +index 8206c22..0000000 +--- a/first ++++ /dev/null +@@ -1,2 +0,0 @@ +-1st file +-2nd line 1st file +diff --git a/second b/second +deleted file mode 100644 +index 1bbba79..0000000 +--- a/second ++++ /dev/null +@@ -1 +0,0 @@ +-2nd file +diff --git a/secondfile b/secondfile +new file mode 100644 +index 0000000..44c5b58 +--- /dev/null ++++ b/secondfile +@@ -0,0 +1,2 @@ ++1st line 2nd file ++2nd line 2nd file +EOF +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success \ + 'redoing changes adding them without commit them should succeed' ' + git rm first && + git mv second secondfile && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git add secondfile && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e +' + +cat >.diff_expect <<EOF +diff --git a/first b/first +deleted file mode 100644 +index 8206c22..0000000 +--- a/first ++++ /dev/null +@@ -1,2 +0,0 @@ +-1st file +-2nd line 1st file +diff --git a/second b/second +deleted file mode 100644 +index 1bbba79..0000000 +--- a/second ++++ /dev/null +@@ -1 +0,0 @@ +-2nd file +EOF +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success '--mixed reset to HEAD should unadd the files' ' + git reset && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + test "$(git rev-parse ORIG_HEAD)" = \ + ddaefe00f1da16864591c61fdc7adb5d7cd6b74e +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success 'redoing the last two commits should succeed' ' + git add secondfile && + git reset --hard ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + + git rm first && + git mv second secondfile && + git commit -a -m "remove 1st and rename 2nd" && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git commit -a -m "modify 2nd file" && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +3rd line in branch2 +EOF +test_expect_success '--hard reset to HEAD should clear a failed merge' ' + git branch branch1 && + git branch branch2 && + + git checkout branch1 && + echo "3rd line in branch1" >>secondfile && + git commit -a -m "change in branch1" && + + git checkout branch2 && + echo "3rd line in branch2" >>secondfile && + git commit -a -m "change in branch2" && + + ! git pull . branch1 && + git reset --hard && + check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success \ + '--hard reset to ORIG_HEAD should clear a fast-forward merge' ' + git reset --hard HEAD^ && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && + + git pull . branch1 && + git reset --hard ORIG_HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && + + git checkout master && + git branch -D branch1 branch2 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +cat > expect << EOF +diff --git a/file1 b/file1 +index d00491f..7ed6ff8 100644 +--- a/file1 ++++ b/file1 +@@ -1 +1 @@ +-1 ++5 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 0cfbf08..0000000 +--- a/file2 ++++ /dev/null +@@ -1 +0,0 @@ +-2 +EOF +cat > cached_expect << EOF +diff --git a/file4 b/file4 +new file mode 100644 +index 0000000..b8626c4 +--- /dev/null ++++ b/file4 +@@ -0,0 +1 @@ ++4 +EOF +test_expect_success 'test --mixed <paths>' ' + echo 1 > file1 && + echo 2 > file2 && + git add file1 file2 && + test_tick && + git commit -m files && + git rm file2 && + echo 3 > file3 && + echo 4 > file4 && + echo 5 > file1 && + git add file1 file3 file4 && + ! git reset HEAD -- file1 file2 file3 && + git diff > output && + git diff output expect && + git diff --cached > output && + git diff output cached_expect +' + +test_expect_success 'test resetting the index at give paths' ' + + mkdir sub && + >sub/file1 && + >sub/file2 && + git update-index --add sub/file1 sub/file2 && + T=$(git write-tree) && + ! git reset HEAD sub/file2 && + U=$(git write-tree) && + echo "$T" && + echo "$U" && + ! git diff-index --cached --exit-code "$T" && + test "$T" != "$U" + +' + +test_expect_success 'resetting an unmodified path is a no-op' ' + git reset --hard && + git reset -- file1 && + git diff-files --exit-code && + git diff-index --cached --exit-code HEAD +' + +cat > expect << EOF +file2: needs update +EOF + +test_expect_success '--mixed refreshes the index' ' + echo 123 >> file2 && + git reset --mixed HEAD > output && + git diff --exit-code expect output +' + +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index ed2e9ee3c6..55558aba8b 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -77,7 +77,7 @@ test_expect_success "checkout with dirty tree without -m" ' test_expect_success "checkout -m with dirty tree" ' git checkout -f master && - git clean && + git clean -f && fill 0 1 2 3 4 5 6 7 8 >one && git checkout -m side && @@ -99,7 +99,7 @@ test_expect_success "checkout -m with dirty tree" ' test_expect_success "checkout -m with dirty tree, renamed" ' - git checkout -f master && git clean && + git checkout -f master && git clean -f && fill 1 2 3 4 5 7 8 >one && if git checkout renamer @@ -121,7 +121,7 @@ test_expect_success "checkout -m with dirty tree, renamed" ' test_expect_success 'checkout -m with merge conflict' ' - git checkout -f master && git clean && + git checkout -f master && git clean -f && fill 1 T 3 4 5 6 S 8 >one && if git checkout renamer @@ -144,7 +144,7 @@ test_expect_success 'checkout -m with merge conflict' ' test_expect_success 'checkout to detach HEAD' ' - git checkout -f renamer && git clean && + git checkout -f renamer && git clean -f && git checkout renamer^ && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -160,7 +160,7 @@ test_expect_success 'checkout to detach HEAD' ' test_expect_success 'checkout to detach HEAD with branchname^' ' - git checkout -f master && git clean && + git checkout -f master && git clean -f && git checkout renamer^ && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -176,7 +176,7 @@ test_expect_success 'checkout to detach HEAD with branchname^' ' test_expect_success 'checkout to detach HEAD with HEAD^0' ' - git checkout -f master && git clean && + git checkout -f master && git clean -f && git checkout HEAD^0 && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 0ed4ae2827..f013c176ed 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -7,6 +7,8 @@ test_description='git-clean basic tests' . ./test-lib.sh +git config clean.requireForce no + test_expect_success 'setup' ' mkdir -p src && @@ -37,6 +39,93 @@ test_expect_success 'git-clean' ' ' +test_expect_success 'git-clean src/' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean src/ && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean src/ src/' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean src/ src/ && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean with prefix' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + (cd src/ && git-clean) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' +test_expect_success 'git-clean -d with prefix and path' ' + + mkdir -p build docs src/feature && + touch a.out src/part3.c src/feature/file.c docs/manual.txt obj.o build/lib.so && + (cd src/ && git-clean -d feature/) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f src/part3.c && + test ! -f src/feature/file.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean symbolic link' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + ln -s docs/manual.txt src/part4.c + git-clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -f src/part4.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + test_expect_success 'git-clean -n' ' mkdir -p build docs && @@ -71,6 +160,24 @@ test_expect_success 'git-clean -d' ' ' +test_expect_success 'git-clean -d src/ examples/' ' + + mkdir -p build docs examples && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so examples/1.c && + git-clean -d src/ examples/ && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f src/part3.c && + test ! -f examples/1.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + test_expect_success 'git-clean -x' ' mkdir -p build docs && @@ -139,6 +246,13 @@ test_expect_success 'git-clean -d -X' ' ' +test_expect_success 'clean.requireForce defaults to true' ' + + git config --unset clean.requireForce && + ! git-clean + +' + test_expect_success 'clean.requireForce' ' git config clean.requireForce true && diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 26bd8ee469..cf389b81da 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -81,7 +81,7 @@ test_expect_success 'explicit commit message should override template' ' git add foo && GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" \ -m "command line msg" && - commit_msg_is "command line msg<unknown>" + commit_msg_is "command line msg" ' test_expect_success 'commit message from file should override template' ' @@ -90,7 +90,7 @@ test_expect_success 'commit message from file should override template' ' echo "standard input msg" | GIT_EDITOR=../t7500/add-content git commit \ --template "$TEMPLATE" --file - && - commit_msg_is "standard input msg<unknown>" + commit_msg_is "standard input msg" ' test_expect_success 'using alternate GIT_INDEX_FILE (1)' ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 7f25689bb7..31a6f63399 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -79,7 +79,8 @@ test_expect_success \ cat >editor <<\EOF #!/bin/sh -sed -i -e "s/a file/an amend commit/g" $1 +sed -e "s/a file/an amend commit/g" < $1 > $1- +mv $1- $1 EOF chmod 755 editor @@ -98,7 +99,8 @@ test_expect_success \ cat >editor <<\EOF #!/bin/sh -sed -i -e "s/amend/older/g" $1 +sed -e "s/amend/older/g" < $1 > $1- +mv $1- $1 EOF chmod 755 editor @@ -173,4 +175,73 @@ test_expect_success 'partial commit that involves removal (3)' ' ' +author="The Real Author <someguy@his.email.org>" +test_expect_success 'amend commit to fix author' ' + + oldtick=$GIT_AUTHOR_DATE && + test_tick && + git reset --hard && + git cat-file -p HEAD | + sed -e "s/author.*/author $author $oldtick/" \ + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ + expected && + git commit --amend --author="$author" && + git cat-file -p HEAD > current && + diff expected current + +' + +test_expect_success 'sign off (1)' ' + + echo 1 >positive && + git add positive && + git commit -s -m "thank you" && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + ( + echo thank you + echo + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + diff -u expected actual + +' + +test_expect_success 'sign off (2)' ' + + echo 2 >positive && + git add positive && + existing="Signed-off-by: Watch This <watchthis@example.com>" && + git commit -s -m "thank you + +$existing" && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + ( + echo thank you + echo + echo $existing + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + diff -u expected actual + +' + +test_expect_success 'multiple -m' ' + + >negative && + git add negative && + git commit -m "one" -m "two" -m "three" && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + ( + echo one + echo + echo two + echo + echo three + ) >expected && + diff -u expected actual + +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh new file mode 100755 index 0000000000..6424c6e2c0 --- /dev/null +++ b/t/t7600-merge.sh @@ -0,0 +1,440 @@ +#!/bin/sh +# +# Copyright (c) 2007 Lars Hjemli +# + +test_description='git-merge + +Testing basic merge operations/option parsing.' + +. ./test-lib.sh + +cat >file <<EOF +1 +2 +3 +4 +5 +6 +7 +8 +9 +EOF + +cat >file.1 <<EOF +1 X +2 +3 +4 +5 +6 +7 +8 +9 +EOF + +cat >file.5 <<EOF +1 +2 +3 +4 +5 X +6 +7 +8 +9 +EOF + +cat >file.9 <<EOF +1 +2 +3 +4 +5 +6 +7 +8 +9 X +EOF + +cat >result.1 <<EOF +1 X +2 +3 +4 +5 +6 +7 +8 +9 +EOF + +cat >result.1-5 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 +EOF + +cat >result.1-5-9 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 X +EOF + +create_merge_msgs() { + echo "Merge commit 'c2'" >msg.1-5 && + echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && + echo "Squashed commit of the following:" >squash.1 && + echo >>squash.1 && + git log --no-merges ^HEAD c1 >>squash.1 && + echo "Squashed commit of the following:" >squash.1-5 && + echo >>squash.1-5 && + git log --no-merges ^HEAD c2 >>squash.1-5 && + echo "Squashed commit of the following:" >squash.1-5-9 && + echo >>squash.1-5-9 && + git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 +} + +verify_diff() { + if ! diff -u "$1" "$2" + then + echo "$3" + false + fi +} + +verify_merge() { + verify_diff "$2" "$1" "[OOPS] bad merge result" && + if test $(git ls-files -u | wc -l) -gt 0 + then + echo "[OOPS] unmerged files" + false + fi && + if ! git diff --exit-code + then + echo "[OOPS] working tree != index" + false + fi && + if test -n "$3" + then + git show -s --pretty=format:%s HEAD >msg.act && + verify_diff "$3" msg.act "[OOPS] bad merge message" + fi +} + +verify_head() { + if test "$1" != "$(git rev-parse HEAD)" + then + echo "[OOPS] HEAD != $1" + false + fi +} + +verify_parents() { + i=1 + while test $# -gt 0 + do + if test "$1" != "$(git rev-parse HEAD^$i)" + then + echo "[OOPS] HEAD^$i != $1" + return 1 + fi + i=$(expr $i + 1) + shift + done +} + +verify_mergeheads() { + i=1 + if ! test -f .git/MERGE_HEAD + then + echo "[OOPS] MERGE_HEAD is missing" + false + fi && + while test $# -gt 0 + do + head=$(head -n $i .git/MERGE_HEAD | tail -n 1) + if test "$1" != "$head" + then + echo "[OOPS] MERGE_HEAD $i != $1" + return 1 + fi + i=$(expr $i + 1) + shift + done +} + +verify_no_mergehead() { + if test -f .git/MERGE_HEAD + then + echo "[OOPS] MERGE_HEAD exists" + false + fi +} + + +test_expect_success 'setup' ' + git add file && + test_tick && + git commit -m "commit 0" && + git tag c0 && + c0=$(git rev-parse HEAD) && + cp file.1 file && + git add file && + test_tick && + git commit -m "commit 1" && + git tag c1 && + c1=$(git rev-parse HEAD) && + git reset --hard "$c0" && + cp file.5 file && + git add file && + test_tick && + git commit -m "commit 2" && + git tag c2 && + c2=$(git rev-parse HEAD) && + git reset --hard "$c0" && + cp file.9 file && + git add file && + test_tick && + git commit -m "commit 3" && + git tag c3 && + c3=$(git rev-parse HEAD) + git reset --hard "$c0" && + create_merge_msgs +' + +test_debug 'gitk --all' + +test_expect_success 'test option parsing' ' + if git merge -$ c1 + then + echo "[OOPS] -$ accepted" + false + fi && + if git merge --no-such c1 + then + echo "[OOPS] --no-such accepted" + false + fi && + if git merge -s foobar c1 + then + echo "[OOPS] -s foobar accepted" + false + fi && + if git merge -s=foobar c1 + then + echo "[OOPS] -s=foobar accepted" + false + fi && + if git merge -m + then + echo "[OOPS] missing commit msg accepted" + false + fi && + if git merge + then + echo "[OOPS] missing commit references accepted" + false + fi +' + +test_expect_success 'merge c0 with c1' ' + git reset --hard c0 && + git merge c1 && + verify_merge file result.1 && + verify_head "$c1" +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2' ' + git reset --hard c1 && + test_tick && + git merge c2 && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 and c3' ' + git reset --hard c1 && + test_tick && + git merge c2 c3 && + verify_merge file result.1-5-9 msg.1-5-9 && + verify_parents $c1 $c2 $c3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (no-commit)' ' + git reset --hard c0 && + git merge --no-commit c1 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (no-commit)' ' + git reset --hard c1 && + git merge --no-commit c2 && + verify_merge file result.1-5 && + verify_head $c1 && + verify_mergeheads $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 and c3 (no-commit)' ' + git reset --hard c1 && + git merge --no-commit c2 c3 && + verify_merge file result.1-5-9 && + verify_head $c1 && + verify_mergeheads $c2 $c3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (squash)' ' + git reset --hard c0 && + git merge --squash c1 && + verify_merge file result.1 && + verify_head $c0 && + verify_no_mergehead && + verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (squash)' ' + git reset --hard c1 && + git merge --squash c2 && + verify_merge file result.1-5 && + verify_head $c1 && + verify_no_mergehead && + verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 and c3 (squash)' ' + git reset --hard c1 && + git merge --squash c2 c3 && + verify_merge file result.1-5-9 && + verify_head $c1 && + verify_no_mergehead && + verify_diff squash.1-5-9 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (no-commit in config)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--no-commit" && + git merge c2 && + verify_merge file result.1-5 && + verify_head $c1 && + verify_mergeheads $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (squash in config)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--squash" && + git merge c2 && + verify_merge file result.1-5 && + verify_head $c1 && + verify_no_mergehead && + verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + +test_expect_success 'override config option -n' ' + git reset --hard c1 && + git config branch.master.mergeoptions "-n" && + test_tick && + git merge --summary c2 >diffstat.txt && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 && + if ! grep -e "^ file | \+2 +-$" diffstat.txt + then + echo "[OOPS] diffstat was not generated" + fi +' + +test_debug 'gitk --all' + +test_expect_success 'override config option --summary' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--summary" && + test_tick && + git merge -n c2 >diffstat.txt && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 && + if grep -e "^ file | \+2 +-$" diffstat.txt + then + echo "[OOPS] diffstat was generated" + false + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (override --no-commit)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--no-commit" && + test_tick && + git merge --commit c2 && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (override --squash)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--squash" && + test_tick && + git merge --no-squash c2 && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (no-ff)' ' + git reset --hard c0 && + test_tick && + git merge --no-ff c1 && + verify_merge file result.1 && + verify_parents $c0 $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "--no-ff" && + git merge --ff c1 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 83f9470202..659f9c758f 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -41,4 +41,41 @@ test_expect_success \ 'Verify commandline' \ 'diff commandline expected' +cat >expected-show-all-headers <<\EOF +0001-Second.patch +(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com> +From: Example <from@example.com> +To: to@example.com +Cc: cc@example.com, A <author@example.com> +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING +In-Reply-To: <unique-message-id@example.com> +References: <unique-message-id@example.com> + +Result: OK +EOF + +test_expect_success 'Show all headers' ' + git send-email \ + --dry-run \ + --from="Example <from@example.com>" \ + --to=to@example.com \ + --cc=cc@example.com \ + --bcc=bcc@example.com \ + --in-reply-to="<unique-message-id@example.com>" \ + --smtp-server relay.example.com \ + $patches | + sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ + -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ + -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \ + >actual-show-all-headers && + diff -u expected-show-all-headers actual-show-all-headers +' + test_done diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 02c41697de..d7a704754e 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -126,20 +126,22 @@ cat > show-ignore.expect <<\EOF # / /no-such-file* -# deeply +# /deeply/ /deeply/no-such-file* -# deeply/nested +# /deeply/nested/ /deeply/nested/no-such-file* -# deeply/nested/directory +# /deeply/nested/directory/ /deeply/nested/directory/no-such-file* EOF test_expect_success 'test show-ignore' " cd test_wc && mkdir -p deeply/nested/directory && + touch deeply/nested/directory/.keep && svn add deeply && + svn up && svn propset -R svn:ignore 'no-such-file*' . svn commit -m 'propset svn:ignore' cd .. && @@ -147,4 +149,69 @@ test_expect_success 'test show-ignore' " cmp show-ignore.expect show-ignore.got " +cat >create-ignore.expect <<\EOF +/no-such-file* +EOF + +cat >create-ignore-index.expect <<\EOF +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 .gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/.gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/nested/.gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/nested/directory/.gitignore +EOF + +test_expect_success 'test create-ignore' " + git-svn fetch && git pull . remotes/git-svn && + git-svn create-ignore && + cmp ./.gitignore create-ignore.expect && + cmp ./deeply/.gitignore create-ignore.expect && + cmp ./deeply/nested/.gitignore create-ignore.expect && + cmp ./deeply/nested/directory/.gitignore create-ignore.expect && + git ls-files -s | grep gitignore | cmp - create-ignore-index.expect + " + +cat >prop.expect <<\EOF +no-such-file* + +EOF +cat >prop2.expect <<\EOF +8 +EOF + +# This test can be improved: since all the svn:ignore contain the same +# pattern, it can pass even though the propget did not execute on the +# right directory. +test_expect_success 'test propget' " + git-svn propget svn:ignore . | cmp - prop.expect && + cd deeply && + git-svn propget svn:ignore . | cmp - ../prop.expect && + git-svn propget svn:entry:committed-rev nested/directory/.keep \ + | cmp - ../prop2.expect && + git-svn propget svn:ignore .. | cmp - ../prop.expect && + git-svn propget svn:ignore nested/ | cmp - ../prop.expect && + git-svn propget svn:ignore ./nested | cmp - ../prop.expect && + git-svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect + " + +cat >prop.expect <<\EOF +Properties on '.': + svn:entry:committed-date + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid + svn:ignore +EOF +cat >prop2.expect <<\EOF +Properties on 'nested/directory/.keep': + svn:entry:committed-date + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid +EOF + +test_expect_success 'test proplist' " + git-svn proplist . | cmp - prop.expect && + git-svn proplist nested/directory/.keep | cmp - prop2.expect + " + test_done diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index d8f9cab35d..7ba76309ac 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -19,8 +19,7 @@ test_expect_success 'initialize repo' " poke trunk/readme && svn commit -m 'another commit' && svn up && - svn mv -m 'rename to thunk' trunk thunk && - svn up && + svn mv trunk thunk && echo goodbye >> thunk/readme && poke thunk/readme && svn commit -m 'bye now' && @@ -52,8 +51,10 @@ test_expect_success 'init and fetch from one svn-remote' " " test_expect_success 'follow deleted parent' " - svn cp -m 'resurrecting trunk as junk' \ - -r2 $svnrepo/trunk $svnrepo/junk && + (svn cp -m 'resurrecting trunk as junk' \ + $svnrepo/trunk@2 $svnrepo/junk || + svn cp -m 'resurrecting trunk as junk' \ + -r2 $svnrepo/trunk $svnrepo/junk) && git config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch -i svn/thunk && diff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9106-git-svn-dcommit-clobber-series.sh index d59acc8d1a..745254665d 100755 --- a/t/t9106-git-svn-dcommit-clobber-series.sh +++ b/t/t9106-git-svn-dcommit-clobber-series.sh @@ -22,6 +22,7 @@ test_expect_success '(supposedly) non-conflicting change from SVN' " cd tmp && perl -i -p -e 's/^58\$/5588/' file && perl -i -p -e 's/^61\$/6611/' file && + poke file && test x\"\`sed -n -e 58p < file\`\" = x5588 && test x\"\`sed -n -e 61p < file\`\" = x6611 && svn commit -m '58 => 5588, 61 => 6611' && diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh index 0d4e6b3f04..902ed4145d 100755 --- a/t/t9116-git-svn-log.sh +++ b/t/t9116-git-svn-log.sh @@ -30,6 +30,12 @@ test_expect_success 'setup repository and import' " git reset --hard trunk && echo aye >> README && git commit -a -m aye && + git svn dcommit && + git reset --hard b && + echo spy >> README && + git commit -a -m spy && + echo try >> README && + git commit -a -m try && git svn dcommit " @@ -45,4 +51,78 @@ test_expect_success 'run log against a from trunk' " git svn log -r3 a | grep ^r3 " +printf 'r1 \nr2 \nr4 \n' > expected-range-r1-r2-r4 + +test_expect_success 'test ascending revision range' " + git reset --hard trunk && + git svn log -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r1-r2-r4 - + " + +printf 'r4 \nr2 \nr1 \n' > expected-range-r4-r2-r1 + +test_expect_success 'test descending revision range' " + git reset --hard trunk && + git svn log -r 4:1 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4-r2-r1 - + " + +printf 'r1 \nr2 \n' > expected-range-r1-r2 + +test_expect_success 'test ascending revision range with unreachable revision' " + git reset --hard trunk && + git svn log -r 1:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r1-r2 - + " + +printf 'r2 \nr1 \n' > expected-range-r2-r1 + +test_expect_success 'test descending revision range with unreachable revision' " + git reset --hard trunk && + git svn log -r 3:1 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2-r1 - + " + +printf 'r2 \n' > expected-range-r2 + +test_expect_success 'test ascending revision range with unreachable upper boundary revision and 1 commit' " + git reset --hard trunk && + git svn log -r 2:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2 - + " + +test_expect_success 'test descending revision range with unreachable upper boundary revision and 1 commit' " + git reset --hard trunk && + git svn log -r 3:2 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2 - + " + +printf 'r4 \n' > expected-range-r4 + +test_expect_success 'test ascending revision range with unreachable lower boundary revision and 1 commit' " + git reset --hard trunk && + git svn log -r 3:4 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 - + " + +test_expect_success 'test descending revision range with unreachable lower boundary revision and 1 commit' " + git reset --hard trunk && + git svn log -r 4:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 - + " + +printf -- '------------------------------------------------------------------------\n' > expected-separator + +test_expect_success 'test ascending revision range with unreachable boundary revisions and no commits' " + git reset --hard trunk && + git svn log -r 5:6 | diff -u expected-separator - + " + +test_expect_success 'test descending revision range with unreachable boundary revisions and no commits' " + git reset --hard trunk && + git svn log -r 6:5 | diff -u expected-separator - + " + +test_expect_success 'test ascending revision range with unreachable boundary revisions and 1 commit' " + git reset --hard trunk && + git svn log -r 3:5 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 - + " + +test_expect_success 'test descending revision range with unreachable boundary revisions and 1 commit' " + git reset --hard trunk && + git svn log -r 5:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 - + " + test_done diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh new file mode 100755 index 0000000000..d482b407f2 --- /dev/null +++ b/t/t9117-git-svn-init-clone.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn init/clone tests' + +. ./lib-git-svn.sh + +# setup, run inside tmp so we don't have any conflicts with $svnrepo +set -e +rm -r .git +mkdir tmp +cd tmp + +test_expect_success 'setup svnrepo' " + mkdir project project/trunk project/branches project/tags && + echo foo > project/trunk/foo && + svn import -m '$test_description' project $svnrepo/project && + rm -rf project + " + +test_expect_success 'basic clone' " + test ! -d trunk && + git svn clone $svnrepo/project/trunk && + test -d trunk/.git/svn && + test -e trunk/foo && + rm -rf trunk + " + +test_expect_success 'clone to target directory' " + test ! -d target && + git svn clone $svnrepo/project/trunk target && + test -d target/.git/svn && + test -e target/foo && + rm -rf target + " + +test_expect_success 'clone with --stdlayout' " + test ! -d project && + git svn clone -s $svnrepo/project && + test -d project/.git/svn && + test -e project/foo && + rm -rf project + " + +test_expect_success 'clone to target directory with --stdlayout' " + test ! -d target && + git svn clone -s $svnrepo/project target && + test -d target/.git/svn && + test -e target/foo && + rm -rf target + " + +test_done diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh new file mode 100755 index 0000000000..640bb066f3 --- /dev/null +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn funky branch names' +. ./lib-git-svn.sh + +test_expect_success 'setup svnrepo' " + mkdir project project/trunk project/branches project/tags && + echo foo > project/trunk/foo && + svn import -m '$test_description' project \"$svnrepo/pr ject\" && + rm -rf project && + svn cp -m 'fun' \"$svnrepo/pr ject/trunk\" \ + \"$svnrepo/pr ject/branches/fun plugin\" && + svn cp -m 'more fun!' \"$svnrepo/pr ject/branches/fun plugin\" \ + \"$svnrepo/pr ject/branches/more fun plugin!\" && + start_httpd + " + +test_expect_success 'test clone with funky branch names' " + git svn clone -s \"$svnrepo/pr ject\" project && + cd project && + git rev-parse 'refs/remotes/fun%20plugin' && + git rev-parse 'refs/remotes/more%20fun%20plugin!' && + cd .. + " + +test_expect_success 'test dcommit to funky branch' " + cd project && + git reset --hard 'refs/remotes/more%20fun%20plugin!' && + echo hello >> foo && + git commit -m 'hello' -- foo && + git svn dcommit && + cd .. + " + +stop_httpd + +test_done diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh new file mode 100755 index 0000000000..439bd93c88 --- /dev/null +++ b/t/t9119-git-svn-info.sh @@ -0,0 +1,368 @@ +#!/bin/sh +# +# Copyright (c) 2007 David D. Kilzer + +test_description='git-svn info' + +. ./lib-git-svn.sh + +ptouch() { + perl -w -e ' + use strict; + die "ptouch requires exactly 2 arguments" if @ARGV != 2; + die "$ARGV[0] does not exist" if ! -e $ARGV[0]; + my @s = stat $ARGV[0]; + utime $s[8], $s[9], $ARGV[1]; + ' "$1" "$2" +} + +test_expect_success 'setup repository and import' " + mkdir info && + cd info && + echo FIRST > A && + echo one > file && + ln -s file symlink-file && + mkdir directory && + touch directory/.placeholder && + ln -s directory symlink-directory && + svn import -m 'initial' . $svnrepo && + cd .. && + mkdir gitwc && + cd gitwc && + git-svn init $svnrepo && + git-svn fetch && + cd .. && + svn co $svnrepo svnwc && + ptouch svnwc/file gitwc/file && + ptouch svnwc/directory gitwc/directory && + ptouch svnwc/symlink-file gitwc/symlink-file && + ptouch svnwc/symlink-directory gitwc/symlink-directory + " + +test_expect_success 'info' " + (cd svnwc; svn info) > expected.info && + (cd gitwc; git-svn info) > actual.info && + git-diff expected.info actual.info + " + +test_expect_success 'info --url' ' + test $(cd gitwc; git-svn info --url) = $svnrepo + ' + +test_expect_success 'info .' " + (cd svnwc; svn info .) > expected.info-dot && + (cd gitwc; git-svn info .) > actual.info-dot && + git-diff expected.info-dot actual.info-dot + " + +test_expect_success 'info --url .' ' + test $(cd gitwc; git-svn info --url .) = $svnrepo + ' + +test_expect_success 'info file' " + (cd svnwc; svn info file) > expected.info-file && + (cd gitwc; git-svn info file) > actual.info-file && + git-diff expected.info-file actual.info-file + " + +test_expect_success 'info --url file' ' + test $(cd gitwc; git-svn info --url file) = "$svnrepo/file" + ' + +test_expect_success 'info directory' " + (cd svnwc; svn info directory) > expected.info-directory && + (cd gitwc; git-svn info directory) > actual.info-directory && + git-diff expected.info-directory actual.info-directory + " + +test_expect_success 'info --url directory' ' + test $(cd gitwc; git-svn info --url directory) = "$svnrepo/directory" + ' + +test_expect_success 'info symlink-file' " + (cd svnwc; svn info symlink-file) > expected.info-symlink-file && + (cd gitwc; git-svn info symlink-file) > actual.info-symlink-file && + git-diff expected.info-symlink-file actual.info-symlink-file + " + +test_expect_success 'info --url symlink-file' ' + test $(cd gitwc; git-svn info --url symlink-file) \ + = "$svnrepo/symlink-file" + ' + +test_expect_success 'info symlink-directory' " + (cd svnwc; svn info symlink-directory) \ + > expected.info-symlink-directory && + (cd gitwc; git-svn info symlink-directory) \ + > actual.info-symlink-directory && + git-diff expected.info-symlink-directory actual.info-symlink-directory + " + +test_expect_success 'info --url symlink-directory' ' + test $(cd gitwc; git-svn info --url symlink-directory) \ + = "$svnrepo/symlink-directory" + ' + +test_expect_success 'info added-file' " + echo two > gitwc/added-file && + cd gitwc && + git add added-file && + cd .. && + cp gitwc/added-file svnwc/added-file && + ptouch gitwc/added-file svnwc/added-file && + cd svnwc && + svn add added-file > /dev/null && + cd .. && + (cd svnwc; svn info added-file) > expected.info-added-file && + (cd gitwc; git-svn info added-file) > actual.info-added-file && + git-diff expected.info-added-file actual.info-added-file + " + +test_expect_success 'info --url added-file' ' + test $(cd gitwc; git-svn info --url added-file) \ + = "$svnrepo/added-file" + ' + +test_expect_success 'info added-directory' " + mkdir gitwc/added-directory svnwc/added-directory && + ptouch gitwc/added-directory svnwc/added-directory && + touch gitwc/added-directory/.placeholder && + cd svnwc && + svn add added-directory > /dev/null && + cd .. && + cd gitwc && + git add added-directory && + cd .. && + (cd svnwc; svn info added-directory) \ + > expected.info-added-directory && + (cd gitwc; git-svn info added-directory) \ + > actual.info-added-directory && + git-diff expected.info-added-directory actual.info-added-directory + " + +test_expect_success 'info --url added-directory' ' + test $(cd gitwc; git-svn info --url added-directory) \ + = "$svnrepo/added-directory" + ' + +test_expect_success 'info added-symlink-file' " + cd gitwc && + ln -s added-file added-symlink-file && + git add added-symlink-file && + cd .. && + cd svnwc && + ln -s added-file added-symlink-file && + svn add added-symlink-file > /dev/null && + cd .. && + ptouch gitwc/added-symlink-file svnwc/added-symlink-file && + (cd svnwc; svn info added-symlink-file) \ + > expected.info-added-symlink-file && + (cd gitwc; git-svn info added-symlink-file) \ + > actual.info-added-symlink-file && + git-diff expected.info-added-symlink-file \ + actual.info-added-symlink-file + " + +test_expect_success 'info --url added-symlink-file' ' + test $(cd gitwc; git-svn info --url added-symlink-file) \ + = "$svnrepo/added-symlink-file" + ' + +test_expect_success 'info added-symlink-directory' " + cd gitwc && + ln -s added-directory added-symlink-directory && + git add added-symlink-directory && + cd .. && + cd svnwc && + ln -s added-directory added-symlink-directory && + svn add added-symlink-directory > /dev/null && + cd .. && + ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory && + (cd svnwc; svn info added-symlink-directory) \ + > expected.info-added-symlink-directory && + (cd gitwc; git-svn info added-symlink-directory) \ + > actual.info-added-symlink-directory && + git-diff expected.info-added-symlink-directory \ + actual.info-added-symlink-directory + " + +test_expect_success 'info --url added-symlink-directory' ' + test $(cd gitwc; git-svn info --url added-symlink-directory) \ + = "$svnrepo/added-symlink-directory" + ' + +# The next few tests replace the "Text Last Updated" value with a +# placeholder since git doesn't have a way to know the date that a +# now-deleted file was last checked out locally. Internally it +# simply reuses the Last Changed Date. + +test_expect_success 'info deleted-file' " + cd gitwc && + git rm -f file > /dev/null && + cd .. && + cd svnwc && + svn rm --force file > /dev/null && + cd .. && + (cd svnwc; svn info file) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > expected.info-deleted-file && + (cd gitwc; git-svn info file) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > actual.info-deleted-file && + git-diff expected.info-deleted-file actual.info-deleted-file + " + +test_expect_success 'info --url file (deleted)' ' + test $(cd gitwc; git-svn info --url file) \ + = "$svnrepo/file" + ' + +test_expect_success 'info deleted-directory' " + cd gitwc && + git rm -r -f directory > /dev/null && + cd .. && + cd svnwc && + svn rm --force directory > /dev/null && + cd .. && + (cd svnwc; svn info directory) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > expected.info-deleted-directory && + (cd gitwc; git-svn info directory) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > actual.info-deleted-directory && + git-diff expected.info-deleted-directory actual.info-deleted-directory + " + +test_expect_success 'info --url directory (deleted)' ' + test $(cd gitwc; git-svn info --url directory) \ + = "$svnrepo/directory" + ' + +test_expect_success 'info deleted-symlink-file' " + cd gitwc && + git rm -f symlink-file > /dev/null && + cd .. && + cd svnwc && + svn rm --force symlink-file > /dev/null && + cd .. && + (cd svnwc; svn info symlink-file) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > expected.info-deleted-symlink-file && + (cd gitwc; git-svn info symlink-file) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > actual.info-deleted-symlink-file && + git-diff expected.info-deleted-symlink-file \ + actual.info-deleted-symlink-file + " + +test_expect_success 'info --url symlink-file (deleted)' ' + test $(cd gitwc; git-svn info --url symlink-file) \ + = "$svnrepo/symlink-file" + ' + +test_expect_success 'info deleted-symlink-directory' " + cd gitwc && + git rm -f symlink-directory > /dev/null && + cd .. && + cd svnwc && + svn rm --force symlink-directory > /dev/null && + cd .. && + (cd svnwc; svn info symlink-directory) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > expected.info-deleted-symlink-directory && + (cd gitwc; git-svn info symlink-directory) | + sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ + > actual.info-deleted-symlink-directory && + git-diff expected.info-deleted-symlink-directory \ + actual.info-deleted-symlink-directory + " + +test_expect_success 'info --url symlink-directory (deleted)' ' + test $(cd gitwc; git-svn info --url symlink-directory) \ + = "$svnrepo/symlink-directory" + ' + +# NOTE: git does not have the concept of replaced objects, +# so we can't test for files in that state. + +test_expect_success 'info unknown-file' " + echo two > gitwc/unknown-file && + cp gitwc/unknown-file svnwc/unknown-file && + ptouch gitwc/unknown-file svnwc/unknown-file && + (cd svnwc; svn info unknown-file) 2> expected.info-unknown-file && + (cd gitwc; git-svn info unknown-file) 2> actual.info-unknown-file && + git-diff expected.info-unknown-file actual.info-unknown-file + " + +test_expect_success 'info --url unknown-file' ' + test -z $(cd gitwc; git-svn info --url unknown-file \ + 2> ../actual.info--url-unknown-file) && + git-diff expected.info-unknown-file actual.info--url-unknown-file + ' + +test_expect_success 'info unknown-directory' " + mkdir gitwc/unknown-directory svnwc/unknown-directory && + ptouch gitwc/unknown-directory svnwc/unknown-directory && + touch gitwc/unknown-directory/.placeholder && + (cd svnwc; svn info unknown-directory) \ + 2> expected.info-unknown-directory && + (cd gitwc; git-svn info unknown-directory) \ + 2> actual.info-unknown-directory && + git-diff expected.info-unknown-directory actual.info-unknown-directory + " + +test_expect_success 'info --url unknown-directory' ' + test -z $(cd gitwc; git-svn info --url unknown-directory \ + 2> ../actual.info--url-unknown-directory) && + git-diff expected.info-unknown-directory \ + actual.info--url-unknown-directory + ' + +test_expect_success 'info unknown-symlink-file' " + cd gitwc && + ln -s unknown-file unknown-symlink-file && + cd .. && + cd svnwc && + ln -s unknown-file unknown-symlink-file && + cd .. && + ptouch gitwc/unknown-symlink-file svnwc/unknown-symlink-file && + (cd svnwc; svn info unknown-symlink-file) \ + 2> expected.info-unknown-symlink-file && + (cd gitwc; git-svn info unknown-symlink-file) \ + 2> actual.info-unknown-symlink-file && + git-diff expected.info-unknown-symlink-file \ + actual.info-unknown-symlink-file + " + +test_expect_success 'info --url unknown-symlink-file' ' + test -z $(cd gitwc; git-svn info --url unknown-symlink-file \ + 2> ../actual.info--url-unknown-symlink-file) && + git-diff expected.info-unknown-symlink-file \ + actual.info--url-unknown-symlink-file + ' + +test_expect_success 'info unknown-symlink-directory' " + cd gitwc && + ln -s unknown-directory unknown-symlink-directory && + cd .. && + cd svnwc && + ln -s unknown-directory unknown-symlink-directory && + cd .. && + ptouch gitwc/unknown-symlink-directory \ + svnwc/unknown-symlink-directory && + (cd svnwc; svn info unknown-symlink-directory) \ + 2> expected.info-unknown-symlink-directory && + (cd gitwc; git-svn info unknown-symlink-directory) \ + 2> actual.info-unknown-symlink-directory && + git-diff expected.info-unknown-symlink-directory \ + actual.info-unknown-symlink-directory + " + +test_expect_success 'info --url unknown-symlink-directory' ' + test -z $(cd gitwc; git-svn info --url unknown-symlink-directory \ + 2> ../actual.info--url-unknown-symlink-directory) && + git-diff expected.info-unknown-symlink-directory \ + actual.info--url-unknown-symlink-directory + ' + +test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 642b836d64..35fff3ddba 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -18,6 +18,7 @@ gitweb_init () { our \$version = "current"; our \$GIT = "git"; our \$projectroot = "$(pwd)"; +our \$project_maxdepth = 8; our \$home_link_str = "projects"; our \$site_name = "[localhost]"; our \$site_header = ""; @@ -30,7 +31,6 @@ our \$projects_list = ""; our \$export_ok = ""; our \$strict_export = ""; -CGI::Carp::set_programname("gitweb/gitweb.cgi"); EOF cat >.git/description <<EOF @@ -557,4 +557,27 @@ test_expect_success \ 'gitweb_run "p=.git;a=tree;opt=--no-merges"' test_debug 'cat gitweb.log' +# ---------------------------------------------------------------------- +# gitweb config and repo config + +cat >>gitweb_config.perl <<EOF + +\$feature{'blame'}{'override'} = 1; +\$feature{'snapshot'}{'override'} = 1; +EOF + +test_expect_success \ + 'config override: tree view, features disabled in repo config' \ + 'git config gitweb.blame no && + git config gitweb.snapshot none && + gitweb_run "p=.git;a=tree"' +test_debug 'cat gitweb.log' + +test_expect_success \ + 'config override: tree view, features enabled in repo config' \ + 'git config gitweb.blame yes && + git config gitweb.snapshot "zip,tgz, tbz2" && + gitweb_run "p=.git;a=tree"' +test_debug 'cat gitweb.log' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index cc1253ccab..90b6844d00 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -59,18 +59,12 @@ esac # ' # . ./test-lib.sh -error () { - echo "* error: $*" - trap - exit - exit 1 -} - -say () { - echo "* $*" -} - -test "${test_description}" != "" || -error "Test script did not set test_description." +[ "x$TERM" != "xdumb" ] && + [ -t 1 ] && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 && + color=t while test "$#" -ne 0 do @@ -80,10 +74,13 @@ do -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) immediate=t; shift ;; -h|--h|--he|--hel|--help) - echo "$test_description" - exit 0 ;; + help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; + -q|--q|--qu|--qui|--quie|--quiet) + quiet=t; shift ;; + --no-color) + color=; shift ;; --no-python) # noop now... shift ;; @@ -92,6 +89,46 @@ do esac done +if test -n "$color"; then + say_color () { + case "$1" in + error) tput bold; tput setaf 1;; # bold red + skip) tput bold; tput setaf 2;; # bold green + pass) tput setaf 2;; # green + info) tput setaf 3;; # brown + *) test -n "$quiet" && return;; + esac + shift + echo "* $*" + tput sgr0 + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + echo "* $*" + } +fi + +error () { + say_color error "error: $*" + trap - exit + exit 1 +} + +say () { + say_color info "$*" +} + +test "${test_description}" != "" || +error "Test script did not set test_description." + +if test "$help" = "t" +then + echo "$test_description" + exit 0 +fi + exec 5>&1 if test "$verbose" = "t" then @@ -122,13 +159,13 @@ test_tick () { test_ok_ () { test_count=$(expr "$test_count" + 1) - say " ok $test_count: $@" + say_color "" " ok $test_count: $@" } test_failure_ () { test_count=$(expr "$test_count" + 1) test_failure=$(expr "$test_failure" + 1); - say "FAIL $test_count: $1" + say_color error "FAIL $test_count: $1" shift echo "$@" | sed -e 's/^/ /' test "$immediate" = "" || { trap - exit; exit 1; } @@ -158,9 +195,9 @@ test_skip () { done case "$to_skip" in t) - say >&3 "skipping test: $@" + say_color skip >&3 "skipping test: $@" test_count=$(expr "$test_count" + 1) - say "skip $test_count: $1" + say_color skip "skip $test_count: $1" : true ;; *) @@ -247,11 +284,11 @@ test_done () { # The Makefile provided will clean this test area so # we will leave things as they are. - say "passed all $test_count test(s)" + say_color pass "passed all $test_count test(s)" exit 0 ;; *) - say "failed $test_failure among $test_count test(s)" + say_color error "failed $test_failure among $test_count test(s)" exit 1 ;; esac @@ -296,8 +333,8 @@ do done case "$to_skip" in t) - say >&3 "skipping test $this_test altogether" - say "skip all tests in $this_test" + say_color skip >&3 "skipping test $this_test altogether" + say_color skip "skip all tests in $this_test" test_done esac done @@ -68,9 +68,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) memcpy(type, type_line + 5, typelen); type[typelen] = '\0'; taglen = sig_line - tag_line - strlen("tag \n"); - item->tag = xmalloc(taglen + 1); - memcpy(item->tag, tag_line + 4, taglen); - item->tag[taglen] = '\0'; + item->tag = xmemdupz(tag_line + 4, taglen); if (!strcmp(type, blob_type)) { item->tagged = &lookup_blob(sha1)->object; diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit index 8b057be304..7092bae263 100644 --- a/templates/hooks--pre-commit +++ b/templates/hooks--pre-commit @@ -58,7 +58,7 @@ perl -e ' if (/\s$/) { bad_line("trailing whitespace", $_); } - if (/^\s* /) { + if (/^\s* \t/) { bad_line("indent SP followed by a TAB", $_); } if (/^(?:[<>=]){7}/) { diff --git a/templates/hooks--update b/templates/hooks--update index d8c76264be..bd93dd1977 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -10,6 +10,12 @@ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. # # --- Command line @@ -32,18 +38,20 @@ fi # --- Config allowunannotated=$(git-repo-config --bool hooks.allowunannotated) +allowdeletebranch=$(git-repo-config --bool hooks.allowdeletebranch) +allowdeletetag=$(git-repo-config --bool hooks.allowdeletetag) # check for no description -projectdesc=$(sed -e '1p' "$GIT_DIR/description") -if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb" ]; then +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then echo "*** Project description file hasn't been set" >&2 exit 1 fi # --- Check types -# if $newrev is 0000...0000, it's a commit to delete a branch +# if $newrev is 0000...0000, it's a commit to delete a ref. if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then - newrev_type=commit + newrev_type=delete else newrev_type=$(git-cat-file -t $newrev) fi @@ -58,15 +66,36 @@ case "$refname","$newrev_type" in exit 1 fi ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; refs/tags/*,tag) # annotated tag ;; refs/heads/*,commit) # branch ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; refs/remotes/*,commit) # tracking branch ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 diff --git a/test-parse-options.c b/test-parse-options.c new file mode 100644 index 0000000000..4d3e2ec39e --- /dev/null +++ b/test-parse-options.c @@ -0,0 +1,36 @@ +#include "cache.h" +#include "parse-options.h" + +static int boolean = 0; +static int integer = 0; +static char *string = NULL; + +int main(int argc, const char **argv) +{ + const char *usage[] = { + "test-parse-options <options>", + NULL + }; + struct option options[] = { + OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"), + OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_GROUP("string options"), + OPT_STRING('s', "string", &string, "string", "get a string"), + OPT_STRING(0, "string2", &string, "str", "get another string"), + OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), + OPT_END(), + }; + int i; + + argc = parse_options(argc, argv, options, usage, 0); + + printf("boolean: %d\n", boolean); + printf("integer: %d\n", integer); + printf("string: %s\n", string ? string : "(not set)"); + + for (i = 0; i < argc; i++) + printf("arg %02d: %s\n", i, argv[i]); + + return 0; +} @@ -25,33 +25,6 @@ #include "cache.h" #include "quote.h" -/* Stolen from "imap-send.c". */ -int nfvasprintf(char **strp, const char *fmt, va_list ap) -{ - int len; - char tmp[1024]; - - if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || - !(*strp = xmalloc(len + 1))) - die("Fatal: Out of memory\n"); - if (len >= (int)sizeof(tmp)) - vsprintf(*strp, fmt, ap); - else - memcpy(*strp, tmp, len + 1); - return len; -} - -int nfasprintf(char **str, const char *fmt, ...) -{ - int rc; - va_list args; - - va_start(args, fmt); - rc = nfvasprintf(str, fmt, args); - va_end(args); - return rc; -} - /* Get a trace file descriptor from GIT_TRACE env variable. */ static int get_trace_fd(int *need_close) { @@ -64,7 +37,7 @@ static int get_trace_fd(int *need_close) return STDERR_FILENO; if (strlen(trace) == 1 && isdigit(*trace)) return atoi(trace); - if (*trace == '/') { + if (is_absolute_path(trace)) { int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd == -1) { fprintf(stderr, @@ -89,63 +62,65 @@ static int get_trace_fd(int *need_close) static const char err_msg[] = "Could not trace into fd given by " "GIT_TRACE environment variable"; -void trace_printf(const char *format, ...) +void trace_printf(const char *fmt, ...) { - char *trace_str; - va_list rest; - int need_close = 0; - int fd = get_trace_fd(&need_close); + struct strbuf buf; + va_list ap; + int fd, len, need_close = 0; + fd = get_trace_fd(&need_close); if (!fd) return; - va_start(rest, format); - nfvasprintf(&trace_str, format, rest); - va_end(rest); - - write_or_whine_pipe(fd, trace_str, strlen(trace_str), err_msg); + strbuf_init(&buf, 64); + va_start(ap, fmt); + len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&buf)) { + strbuf_grow(&buf, len - strbuf_avail(&buf) + 128); + va_start(ap, fmt); + len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&buf)) + die("broken vsnprintf"); + } + strbuf_setlen(&buf, len); - free(trace_str); + write_or_whine_pipe(fd, buf.buf, buf.len, err_msg); + strbuf_release(&buf); if (need_close) close(fd); } -void trace_argv_printf(const char **argv, int count, const char *format, ...) +void trace_argv_printf(const char **argv, int count, const char *fmt, ...) { - char *argv_str, *format_str, *trace_str; - size_t argv_len, format_len, trace_len; - va_list rest; - int need_close = 0; - int fd = get_trace_fd(&need_close); + struct strbuf buf; + va_list ap; + int fd, len, need_close = 0; + fd = get_trace_fd(&need_close); if (!fd) return; - /* Get the argv string. */ - argv_str = sq_quote_argv(argv, count); - argv_len = strlen(argv_str); - - /* Get the formated string. */ - va_start(rest, format); - nfvasprintf(&format_str, format, rest); - va_end(rest); - - /* Allocate buffer for trace string. */ - format_len = strlen(format_str); - trace_len = argv_len + format_len + 1; /* + 1 for \n */ - trace_str = xmalloc(trace_len + 1); - - /* Copy everything into the trace string. */ - strncpy(trace_str, format_str, format_len); - strncpy(trace_str + format_len, argv_str, argv_len); - strcpy(trace_str + trace_len - 1, "\n"); - - write_or_whine_pipe(fd, trace_str, trace_len, err_msg); + strbuf_init(&buf, 64); + va_start(ap, fmt); + len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&buf)) { + strbuf_grow(&buf, len - strbuf_avail(&buf) + 128); + va_start(ap, fmt); + len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&buf)) + die("broken vsnprintf"); + } + strbuf_setlen(&buf, len); - free(argv_str); - free(format_str); - free(trace_str); + sq_quote_argv(&buf, argv, count, 0); + strbuf_addch(&buf, '\n'); + write_or_whine_pipe(fd, buf.buf, buf.len, err_msg); + strbuf_release(&buf); if (need_close) close(fd); diff --git a/transport.c b/transport.c new file mode 100644 index 0000000000..50db9807d0 --- /dev/null +++ b/transport.c @@ -0,0 +1,809 @@ +#include "cache.h" +#include "transport.h" +#include "run-command.h" +#ifndef NO_CURL +#include "http.h" +#endif +#include "pkt-line.h" +#include "fetch-pack.h" +#include "send-pack.h" +#include "walker.h" +#include "bundle.h" +#include "dir.h" +#include "refs.h" + +/* rsync support */ + +/* + * We copy packed-refs and refs/ into a temporary file, then read the + * loose refs recursively (sorting whenever possible), and then inserting + * those packed refs that are not yet in the list (not validating, but + * assuming that the file is sorted). + * + * Appears refactoring this from refs.c is too cumbersome. + */ + +static int str_cmp(const void *a, const void *b) +{ + const char *s1 = a; + const char *s2 = b; + + return strcmp(s1, s2); +} + +/* path->buf + name_offset is expected to point to "refs/" */ + +static int read_loose_refs(struct strbuf *path, int name_offset, + struct ref **tail) +{ + DIR *dir = opendir(path->buf); + struct dirent *de; + struct { + char **entries; + int nr, alloc; + } list; + int i, pathlen; + + if (!dir) + return -1; + + memset (&list, 0, sizeof(list)); + + while ((de = readdir(dir))) { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && + de->d_name[2] == '\0'))) + continue; + ALLOC_GROW(list.entries, list.nr + 1, list.alloc); + list.entries[list.nr++] = xstrdup(de->d_name); + } + closedir(dir); + + /* sort the list */ + + qsort(list.entries, list.nr, sizeof(char *), str_cmp); + + pathlen = path->len; + strbuf_addch(path, '/'); + + for (i = 0; i < list.nr; i++, strbuf_setlen(path, pathlen + 1)) { + strbuf_addstr(path, list.entries[i]); + if (read_loose_refs(path, name_offset, tail)) { + int fd = open(path->buf, O_RDONLY); + char buffer[40]; + struct ref *next; + + if (fd < 0) + continue; + next = alloc_ref(path->len - name_offset + 1); + if (read_in_full(fd, buffer, 40) != 40 || + get_sha1_hex(buffer, next->old_sha1)) { + close(fd); + free(next); + continue; + } + close(fd); + strcpy(next->name, path->buf + name_offset); + (*tail)->next = next; + *tail = next; + } + } + strbuf_setlen(path, pathlen); + + for (i = 0; i < list.nr; i++) + free(list.entries[i]); + free(list.entries); + + return 0; +} + +/* insert the packed refs for which no loose refs were found */ + +static void insert_packed_refs(const char *packed_refs, struct ref **list) +{ + FILE *f = fopen(packed_refs, "r"); + static char buffer[PATH_MAX]; + + if (!f) + return; + + for (;;) { + int cmp = cmp, len; + + if (!fgets(buffer, sizeof(buffer), f)) { + fclose(f); + return; + } + + if (hexval(buffer[0]) > 0xf) + continue; + len = strlen(buffer); + if (buffer[len - 1] == '\n') + buffer[--len] = '\0'; + if (len < 41) + continue; + while ((*list)->next && + (cmp = strcmp(buffer + 41, + (*list)->next->name)) > 0) + list = &(*list)->next; + if (!(*list)->next || cmp < 0) { + struct ref *next = alloc_ref(len - 40); + buffer[40] = '\0'; + if (get_sha1_hex(buffer, next->old_sha1)) { + warning ("invalid SHA-1: %s", buffer); + free(next); + continue; + } + strcpy(next->name, buffer + 41); + next->next = (*list)->next; + (*list)->next = next; + list = &(*list)->next; + } + } +} + +static struct ref *get_refs_via_rsync(struct transport *transport) +{ + struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; + struct ref dummy, *tail = &dummy; + struct child_process rsync; + const char *args[5]; + int temp_dir_len; + + /* copy the refs to the temporary directory */ + + strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX")); + if (!mkdtemp(temp_dir.buf)) + die ("Could not make temporary directory"); + temp_dir_len = temp_dir.len; + + strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, "/refs"); + + memset(&rsync, 0, sizeof(rsync)); + rsync.argv = args; + rsync.stdout_to_stderr = 1; + args[0] = "rsync"; + args[1] = (transport->verbose > 0) ? "-rv" : "-r"; + args[2] = buf.buf; + args[3] = temp_dir.buf; + args[4] = NULL; + + if (run_command(&rsync)) + die ("Could not run rsync to get refs"); + + strbuf_reset(&buf); + strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, "/packed-refs"); + + args[2] = buf.buf; + + if (run_command(&rsync)) + die ("Could not run rsync to get refs"); + + /* read the copied refs */ + + strbuf_addstr(&temp_dir, "/refs"); + read_loose_refs(&temp_dir, temp_dir_len + 1, &tail); + strbuf_setlen(&temp_dir, temp_dir_len); + + tail = &dummy; + strbuf_addstr(&temp_dir, "/packed-refs"); + insert_packed_refs(temp_dir.buf, &tail); + strbuf_setlen(&temp_dir, temp_dir_len); + + if (remove_dir_recursively(&temp_dir, 0)) + warning ("Error removing temporary directory %s.", + temp_dir.buf); + + strbuf_release(&buf); + strbuf_release(&temp_dir); + + return dummy.next; +} + +static int fetch_objs_via_rsync(struct transport *transport, + int nr_objs, struct ref **to_fetch) +{ + struct strbuf buf = STRBUF_INIT; + struct child_process rsync; + const char *args[8]; + int result; + + strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, "/objects/"); + + memset(&rsync, 0, sizeof(rsync)); + rsync.argv = args; + rsync.stdout_to_stderr = 1; + args[0] = "rsync"; + args[1] = (transport->verbose > 0) ? "-rv" : "-r"; + args[2] = "--ignore-existing"; + args[3] = "--exclude"; + args[4] = "info"; + args[5] = buf.buf; + args[6] = get_object_directory(); + args[7] = NULL; + + /* NEEDSWORK: handle one level of alternates */ + result = run_command(&rsync); + + strbuf_release(&buf); + + return result; +} + +static int write_one_ref(const char *name, const unsigned char *sha1, + int flags, void *data) +{ + struct strbuf *buf = data; + int len = buf->len; + FILE *f; + + /* when called via for_each_ref(), flags is non-zero */ + if (flags && prefixcmp(name, "refs/heads/") && + prefixcmp(name, "refs/tags/")) + return 0; + + strbuf_addstr(buf, name); + if (safe_create_leading_directories(buf->buf) || + !(f = fopen(buf->buf, "w")) || + fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 || + fclose(f)) + return error("problems writing temporary file %s", buf->buf); + strbuf_setlen(buf, len); + return 0; +} + +static int write_refs_to_temp_dir(struct strbuf *temp_dir, + int refspec_nr, const char **refspec) +{ + int i; + + for (i = 0; i < refspec_nr; i++) { + unsigned char sha1[20]; + char *ref; + + if (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1) + return error("Could not get ref %s", refspec[i]); + + if (write_one_ref(ref, sha1, 0, temp_dir)) { + free(ref); + return -1; + } + free(ref); + } + return 0; +} + +static int rsync_transport_push(struct transport *transport, + int refspec_nr, const char **refspec, int flags) +{ + struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; + int result = 0, i; + struct child_process rsync; + const char *args[10]; + + if (flags & TRANSPORT_PUSH_MIRROR) + return error("rsync transport does not support mirror mode"); + + /* first push the objects */ + + strbuf_addstr(&buf, transport->url); + strbuf_addch(&buf, '/'); + + memset(&rsync, 0, sizeof(rsync)); + rsync.argv = args; + rsync.stdout_to_stderr = 1; + i = 0; + args[i++] = "rsync"; + args[i++] = "-a"; + if (flags & TRANSPORT_PUSH_DRY_RUN) + args[i++] = "--dry-run"; + if (transport->verbose > 0) + args[i++] = "-v"; + args[i++] = "--ignore-existing"; + args[i++] = "--exclude"; + args[i++] = "info"; + args[i++] = get_object_directory(); + args[i++] = buf.buf; + args[i++] = NULL; + + if (run_command(&rsync)) + return error("Could not push objects to %s", transport->url); + + /* copy the refs to the temporary directory; they could be packed. */ + + strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX")); + if (!mkdtemp(temp_dir.buf)) + die ("Could not make temporary directory"); + strbuf_addch(&temp_dir, '/'); + + if (flags & TRANSPORT_PUSH_ALL) { + if (for_each_ref(write_one_ref, &temp_dir)) + return -1; + } else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec)) + return -1; + + i = 2; + if (flags & TRANSPORT_PUSH_DRY_RUN) + args[i++] = "--dry-run"; + if (!(flags & TRANSPORT_PUSH_FORCE)) + args[i++] = "--ignore-existing"; + args[i++] = temp_dir.buf; + args[i++] = transport->url; + args[i++] = NULL; + if (run_command(&rsync)) + result = error("Could not push to %s", transport->url); + + if (remove_dir_recursively(&temp_dir, 0)) + warning ("Could not remove temporary directory %s.", + temp_dir.buf); + + strbuf_release(&buf); + strbuf_release(&temp_dir); + + return result; +} + +/* Generic functions for using commit walkers */ + +#ifndef NO_CURL /* http fetch is the only user */ +static int fetch_objs_via_walker(struct transport *transport, + int nr_objs, struct ref **to_fetch) +{ + char *dest = xstrdup(transport->url); + struct walker *walker = transport->data; + char **objs = xmalloc(nr_objs * sizeof(*objs)); + int i; + + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = transport->verbose >= 0; + walker->get_recover = 0; + + for (i = 0; i < nr_objs; i++) + objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); + + if (walker_fetch(walker, nr_objs, objs, NULL, NULL)) + die("Fetch failed."); + + for (i = 0; i < nr_objs; i++) + free(objs[i]); + free(objs); + free(dest); + return 0; +} +#endif /* NO_CURL */ + +static int disconnect_walker(struct transport *transport) +{ + struct walker *walker = transport->data; + if (walker) + walker_free(walker); + return 0; +} + +#ifndef NO_CURL +static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) +{ + const char **argv; + int argc; + int err; + + if (flags & TRANSPORT_PUSH_MIRROR) + return error("http transport does not support mirror mode"); + + argv = xmalloc((refspec_nr + 12) * sizeof(char *)); + argv[0] = "http-push"; + argc = 1; + if (flags & TRANSPORT_PUSH_ALL) + argv[argc++] = "--all"; + if (flags & TRANSPORT_PUSH_FORCE) + argv[argc++] = "--force"; + if (flags & TRANSPORT_PUSH_DRY_RUN) + argv[argc++] = "--dry-run"; + if (flags & TRANSPORT_PUSH_VERBOSE) + argv[argc++] = "--verbose"; + argv[argc++] = transport->url; + while (refspec_nr--) + argv[argc++] = *refspec++; + argv[argc] = NULL; + err = run_command_v_opt(argv, RUN_GIT_CMD); + switch (err) { + case -ERR_RUN_COMMAND_FORK: + error("unable to fork for %s", argv[0]); + case -ERR_RUN_COMMAND_EXEC: + error("unable to exec %s", argv[0]); + break; + case -ERR_RUN_COMMAND_WAITPID: + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + error("%s died with strange error", argv[0]); + } + return !!err; +} + +static int missing__target(int code, int result) +{ + return /* file:// URL -- do we ever use one??? */ + (result == CURLE_FILE_COULDNT_READ_FILE) || + /* http:// and https:// URL */ + (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) || + /* ftp:// URL */ + (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE) + ; +} + +#define missing_target(a) missing__target((a)->http_code, (a)->curl_result) + +static struct ref *get_refs_via_curl(struct transport *transport) +{ + struct buffer buffer; + char *data, *start, *mid; + char *ref_name; + char *refs_url; + int i = 0; + + struct active_request_slot *slot; + struct slot_results results; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = xmalloc(4096); + buffer.size = 4096; + buffer.posn = 0; + buffer.buffer = data; + + refs_url = xmalloc(strlen(transport->url) + 11); + sprintf(refs_url, "%s/info/refs", transport->url); + + http_init(); + + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); + curl_easy_setopt(slot->curl, CURLOPT_URL, refs_url); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result != CURLE_OK) { + if (missing_target(&results)) { + free(buffer.buffer); + return NULL; + } else { + free(buffer.buffer); + error("%s", curl_errorstr); + return NULL; + } + } + } else { + free(buffer.buffer); + error("Unable to start request"); + return NULL; + } + + http_cleanup(); + + data = buffer.buffer; + start = NULL; + mid = data; + while (i < buffer.posn) { + if (!start) + start = &data[i]; + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + free(buffer.buffer); + + return refs; +} + +static int fetch_objs_via_curl(struct transport *transport, + int nr_objs, struct ref **to_fetch) +{ + if (!transport->data) + transport->data = get_http_walker(transport->url); + return fetch_objs_via_walker(transport, nr_objs, to_fetch); +} + +#endif + +struct bundle_transport_data { + int fd; + struct bundle_header header; +}; + +static struct ref *get_refs_from_bundle(struct transport *transport) +{ + struct bundle_transport_data *data = transport->data; + struct ref *result = NULL; + int i; + + if (data->fd > 0) + close(data->fd); + data->fd = read_bundle_header(transport->url, &data->header); + if (data->fd < 0) + die ("Could not read bundle '%s'.", transport->url); + for (i = 0; i < data->header.references.nr; i++) { + struct ref_list_entry *e = data->header.references.list + i; + struct ref *ref = alloc_ref(strlen(e->name) + 1); + hashcpy(ref->old_sha1, e->sha1); + strcpy(ref->name, e->name); + ref->next = result; + result = ref; + } + return result; +} + +static int fetch_refs_from_bundle(struct transport *transport, + int nr_heads, struct ref **to_fetch) +{ + struct bundle_transport_data *data = transport->data; + return unbundle(&data->header, data->fd); +} + +static int close_bundle(struct transport *transport) +{ + struct bundle_transport_data *data = transport->data; + if (data->fd > 0) + close(data->fd); + free(data); + return 0; +} + +struct git_transport_data { + unsigned thin : 1; + unsigned keep : 1; + int depth; + const char *uploadpack; + const char *receivepack; +}; + +static int set_git_option(struct transport *connection, + const char *name, const char *value) +{ + struct git_transport_data *data = connection->data; + if (!strcmp(name, TRANS_OPT_UPLOADPACK)) { + data->uploadpack = value; + return 0; + } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) { + data->receivepack = value; + return 0; + } else if (!strcmp(name, TRANS_OPT_THIN)) { + data->thin = !!value; + return 0; + } else if (!strcmp(name, TRANS_OPT_KEEP)) { + data->keep = !!value; + return 0; + } else if (!strcmp(name, TRANS_OPT_DEPTH)) { + if (!value) + data->depth = 0; + else + data->depth = atoi(value); + return 0; + } + return 1; +} + +static struct ref *get_refs_via_connect(struct transport *transport) +{ + struct git_transport_data *data = transport->data; + struct ref *refs; + int fd[2]; + char *dest = xstrdup(transport->url); + struct child_process *conn = git_connect(fd, dest, data->uploadpack, 0); + + get_remote_heads(fd[0], &refs, 0, NULL, 0); + packet_flush(fd[1]); + + finish_connect(conn); + + free(dest); + + return refs; +} + +static int fetch_refs_via_pack(struct transport *transport, + int nr_heads, struct ref **to_fetch) +{ + struct git_transport_data *data = transport->data; + char **heads = xmalloc(nr_heads * sizeof(*heads)); + char **origh = xmalloc(nr_heads * sizeof(*origh)); + struct ref *refs; + char *dest = xstrdup(transport->url); + struct fetch_pack_args args; + int i; + + memset(&args, 0, sizeof(args)); + args.uploadpack = data->uploadpack; + args.keep_pack = data->keep; + args.lock_pack = 1; + args.use_thin_pack = data->thin; + args.verbose = transport->verbose > 0; + args.depth = data->depth; + + for (i = 0; i < nr_heads; i++) + origh[i] = heads[i] = xstrdup(to_fetch[i]->name); + refs = fetch_pack(&args, dest, nr_heads, heads, &transport->pack_lockfile); + + for (i = 0; i < nr_heads; i++) + free(origh[i]); + free(origh); + free(heads); + free_refs(refs); + free(dest); + return 0; +} + +static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) +{ + struct git_transport_data *data = transport->data; + struct send_pack_args args; + + args.receivepack = data->receivepack; + args.send_all = !!(flags & TRANSPORT_PUSH_ALL); + args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); + args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); + args.use_thin_pack = data->thin; + args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); + args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); + + return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec); +} + +static int disconnect_git(struct transport *transport) +{ + free(transport->data); + return 0; +} + +static int is_local(const char *url) +{ + const char *colon = strchr(url, ':'); + const char *slash = strchr(url, '/'); + return !colon || (slash && slash < colon); +} + +static int is_file(const char *url) +{ + struct stat buf; + if (stat(url, &buf)) + return 0; + return S_ISREG(buf.st_mode); +} + +struct transport *transport_get(struct remote *remote, const char *url) +{ + struct transport *ret = xcalloc(1, sizeof(*ret)); + + ret->remote = remote; + ret->url = url; + + if (!prefixcmp(url, "rsync://")) { + ret->get_refs_list = get_refs_via_rsync; + ret->fetch = fetch_objs_via_rsync; + ret->push = rsync_transport_push; + + } else if (!prefixcmp(url, "http://") + || !prefixcmp(url, "https://") + || !prefixcmp(url, "ftp://")) { +#ifdef NO_CURL + error("git was compiled without libcurl support."); +#else + ret->get_refs_list = get_refs_via_curl; + ret->fetch = fetch_objs_via_curl; + ret->push = curl_transport_push; +#endif + ret->disconnect = disconnect_walker; + + } else if (is_local(url) && is_file(url)) { + struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); + ret->data = data; + ret->get_refs_list = get_refs_from_bundle; + ret->fetch = fetch_refs_from_bundle; + ret->disconnect = close_bundle; + + } else { + struct git_transport_data *data = xcalloc(1, sizeof(*data)); + ret->data = data; + ret->set_option = set_git_option; + ret->get_refs_list = get_refs_via_connect; + ret->fetch = fetch_refs_via_pack; + ret->push = git_transport_push; + ret->disconnect = disconnect_git; + + data->thin = 1; + data->uploadpack = "git-upload-pack"; + if (remote && remote->uploadpack) + data->uploadpack = remote->uploadpack; + data->receivepack = "git-receive-pack"; + if (remote && remote->receivepack) + data->receivepack = remote->receivepack; + } + + return ret; +} + +int transport_set_option(struct transport *transport, + const char *name, const char *value) +{ + if (transport->set_option) + return transport->set_option(transport, name, value); + return 1; +} + +int transport_push(struct transport *transport, + int refspec_nr, const char **refspec, int flags) +{ + if (!transport->push) + return 1; + return transport->push(transport, refspec_nr, refspec, flags); +} + +const struct ref *transport_get_remote_refs(struct transport *transport) +{ + if (!transport->remote_refs) + transport->remote_refs = transport->get_refs_list(transport); + return transport->remote_refs; +} + +int transport_fetch_refs(struct transport *transport, struct ref *refs) +{ + int rc; + int nr_heads = 0, nr_alloc = 0; + struct ref **heads = NULL; + struct ref *rm; + + for (rm = refs; rm; rm = rm->next) { + if (rm->peer_ref && + !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1)) + continue; + ALLOC_GROW(heads, nr_heads + 1, nr_alloc); + heads[nr_heads++] = rm; + } + + rc = transport->fetch(transport, nr_heads, heads); + free(heads); + return rc; +} + +void transport_unlock_pack(struct transport *transport) +{ + if (transport->pack_lockfile) { + unlink(transport->pack_lockfile); + free(transport->pack_lockfile); + transport->pack_lockfile = NULL; + } +} + +int transport_disconnect(struct transport *transport) +{ + int ret = 0; + if (transport->disconnect) + ret = transport->disconnect(transport); + free(transport); + return ret; +} diff --git a/transport.h b/transport.h new file mode 100644 index 0000000000..6fb4526cda --- /dev/null +++ b/transport.h @@ -0,0 +1,72 @@ +#ifndef TRANSPORT_H +#define TRANSPORT_H + +#include "cache.h" +#include "remote.h" + +struct transport { + struct remote *remote; + const char *url; + void *data; + const struct ref *remote_refs; + + /** + * Returns 0 if successful, positive if the option is not + * recognized or is inapplicable, and negative if the option + * is applicable but the value is invalid. + **/ + int (*set_option)(struct transport *connection, const char *name, + const char *value); + + struct ref *(*get_refs_list)(struct transport *transport); + int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); + + int (*disconnect)(struct transport *connection); + char *pack_lockfile; + signed verbose : 2; +}; + +#define TRANSPORT_PUSH_ALL 1 +#define TRANSPORT_PUSH_FORCE 2 +#define TRANSPORT_PUSH_DRY_RUN 4 +#define TRANSPORT_PUSH_MIRROR 8 +#define TRANSPORT_PUSH_VERBOSE 16 + +/* Returns a transport suitable for the url */ +struct transport *transport_get(struct remote *, const char *); + +/* Transport options which apply to git:// and scp-style URLs */ + +/* The program to use on the remote side to send a pack */ +#define TRANS_OPT_UPLOADPACK "uploadpack" + +/* The program to use on the remote side to receive a pack */ +#define TRANS_OPT_RECEIVEPACK "receivepack" + +/* Transfer the data as a thin pack if not null */ +#define TRANS_OPT_THIN "thin" + +/* Keep the pack that was transferred if not null */ +#define TRANS_OPT_KEEP "keep" + +/* Limit the depth of the fetch if not null */ +#define TRANS_OPT_DEPTH "depth" + +/** + * Returns 0 if the option was used, non-zero otherwise. Prints a + * message to stderr if the option is not used. + **/ +int transport_set_option(struct transport *transport, const char *name, + const char *value); + +int transport_push(struct transport *connection, + int refspec_nr, const char **refspec, int flags); + +const struct ref *transport_get_remote_refs(struct transport *transport); + +int transport_fetch_refs(struct transport *transport, struct ref *refs); +void transport_unlock_pack(struct transport *transport); +int transport_disconnect(struct transport *transport); + +#endif diff --git a/tree-diff.c b/tree-diff.c index 7c261fd7c3..aa0a100295 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -39,7 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const show_entry(opt, "+", t2, base, baselen); return 1; } - if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) + if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2) return 0; /* @@ -52,10 +52,10 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const return 0; } - if (opt->recursive && S_ISDIR(mode1)) { + if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) { int retval; char *newbase = malloc_base(base, baselen, path1, pathlen1); - if (opt->tree_in_recursive) + if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) opt->change(opt, mode1, mode2, sha1, sha2, base, path1); retval = diff_tree_sha1(sha1, sha2, newbase, opt); @@ -206,7 +206,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree const char *path; const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); - if (opt->recursive && S_ISDIR(mode)) { + if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) { enum object_type type; int pathlen = tree_entry_len(path, sha1); char *newbase = malloc_base(base, baselen, path, pathlen); @@ -257,7 +257,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (opt->quiet && opt->has_changes) + if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { skip_uninteresting(t1, base, baselen, opt); @@ -315,7 +315,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co q->nr = 0; diff_setup(&diff_opts); - diff_opts.recursive = 1; + DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = opt->paths[0]; @@ -380,7 +380,7 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha init_tree_desc(&t1, tree1, size1); init_tree_desc(&t2, tree2, size2); retval = diff_tree(&t1, &t2, base, opt); - if (opt->follow_renames && diff_might_be_rename()) { + if (DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) { init_tree_desc(&t1, tree1, size1); init_tree_desc(&t2, tree2, size2); try_to_follow_renames(&t1, &t2, base, opt); diff --git a/unpack-trees.c b/unpack-trees.c index 9411c67a06..aea16adde8 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -297,7 +297,7 @@ static void check_updates(struct cache_entry **src, int nr, { unsigned short mask = htons(CE_UPDATE); unsigned cnt = 0, total = 0; - struct progress progress; + struct progress *progress = NULL; char last_symlink[PATH_MAX]; if (o->update && o->verbose_update) { @@ -307,8 +307,8 @@ static void check_updates(struct cache_entry **src, int nr, total++; } - start_progress_delay(&progress, "Checking %u files out...", - "", total, 50, 2); + progress = start_progress_delay("Checking out files", + total, 50, 2); cnt = 0; } @@ -316,9 +316,8 @@ static void check_updates(struct cache_entry **src, int nr, while (nr--) { struct cache_entry *ce = *src++; - if (total) - if (!ce->ce_mode || ce->ce_flags & mask) - display_progress(&progress, ++cnt); + if (!ce->ce_mode || ce->ce_flags & mask) + display_progress(progress, ++cnt); if (!ce->ce_mode) { if (o->update) unlink_entry(ce->name, last_symlink); @@ -332,8 +331,7 @@ static void check_updates(struct cache_entry **src, int nr, } } } - if (total) - stop_progress(&progress);; + stop_progress(&progress); } int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) diff --git a/upload-pack.c b/upload-pack.c index fe96ef15c4..7e04311027 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -9,6 +9,7 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "run-command.h" static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; @@ -96,117 +97,87 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } +static int do_rev_list(int fd, void *create_full_pack) +{ + int i; + struct rev_info revs; + + pack_pipe = fdopen(fd, "w"); + if (create_full_pack) + use_thin_pack = 0; /* no point doing it */ + init_revisions(&revs, NULL); + revs.tag_objects = 1; + revs.tree_objects = 1; + revs.blob_objects = 1; + if (use_thin_pack) + revs.edge_hint = 1; + + if (create_full_pack) { + const char *args[] = {"rev-list", "--all", NULL}; + setup_revisions(2, args, &revs, NULL); + } else { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; + /* why??? */ + o->flags &= ~UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; + o->flags |= UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + setup_revisions(0, NULL, &revs, NULL); + } + prepare_revision_walk(&revs); + mark_edges_uninteresting(revs.commits, &revs, show_edge); + traverse_commit_list(&revs, show_commit, show_object); + return 0; +} + static void create_pack_file(void) { - /* Pipes between rev-list to pack-objects, pack-objects to us - * and pack-objects error stream for progress bar. - */ - int lp_pipe[2], pu_pipe[2], pe_pipe[2]; - pid_t pid_rev_list, pid_pack_objects; + struct async rev_list; + struct child_process pack_objects; int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; int buffered = -1; - - if (pipe(lp_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - pid_rev_list = fork(); - if (pid_rev_list < 0) + ssize_t sz; + const char *argv[10]; + int arg = 0; + + rev_list.proc = do_rev_list; + /* .data is just a boolean: any non-NULL value will do */ + rev_list.data = create_full_pack ? &rev_list : NULL; + if (start_async(&rev_list)) die("git-upload-pack: unable to fork git-rev-list"); - if (!pid_rev_list) { - int i; - struct rev_info revs; - - close(lp_pipe[0]); - pack_pipe = fdopen(lp_pipe[1], "w"); - - if (create_full_pack) - use_thin_pack = 0; /* no point doing it */ - init_revisions(&revs, NULL); - revs.tag_objects = 1; - revs.tree_objects = 1; - revs.blob_objects = 1; - if (use_thin_pack) - revs.edge_hint = 1; - - if (create_full_pack) { - const char *args[] = {"rev-list", "--all", NULL}; - setup_revisions(2, args, &revs, NULL); - } else { - for (i = 0; i < want_obj.nr; i++) { - struct object *o = want_obj.objects[i].item; - /* why??? */ - o->flags &= ~UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - for (i = 0; i < have_obj.nr; i++) { - struct object *o = have_obj.objects[i].item; - o->flags |= UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - setup_revisions(0, NULL, &revs, NULL); - } - prepare_revision_walk(&revs); - mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); - exit(0); - } - - if (pipe(pu_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - if (pipe(pe_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - pid_pack_objects = fork(); - if (pid_pack_objects < 0) { - /* daemon sets things up to ignore TERM */ - kill(pid_rev_list, SIGKILL); + argv[arg++] = "pack-objects"; + argv[arg++] = "--stdout"; + if (!no_progress) + argv[arg++] = "--progress"; + if (use_ofs_delta) + argv[arg++] = "--delta-base-offset"; + argv[arg++] = NULL; + + memset(&pack_objects, 0, sizeof(pack_objects)); + pack_objects.in = rev_list.out; /* start_command closes it */ + pack_objects.out = -1; + pack_objects.err = -1; + pack_objects.git_cmd = 1; + pack_objects.argv = argv; + + if (start_command(&pack_objects)) die("git-upload-pack: unable to fork git-pack-objects"); - } - if (!pid_pack_objects) { - const char *argv[10]; - int i = 0; - - dup2(lp_pipe[0], 0); - dup2(pu_pipe[1], 1); - dup2(pe_pipe[1], 2); - - close(lp_pipe[0]); - close(lp_pipe[1]); - close(pu_pipe[0]); - close(pu_pipe[1]); - close(pe_pipe[0]); - close(pe_pipe[1]); - - argv[i++] = "pack-objects"; - argv[i++] = "--stdout"; - if (!no_progress) - argv[i++] = "--progress"; - if (use_ofs_delta) - argv[i++] = "--delta-base-offset"; - argv[i++] = NULL; - - execv_git_cmd(argv); - kill(pid_rev_list, SIGKILL); - die("git-upload-pack: unable to exec git-pack-objects"); - } - close(lp_pipe[0]); - close(lp_pipe[1]); - - /* We read from pe_pipe[0] to capture stderr output for - * progress bar, and pu_pipe[0] to capture the pack data. + /* We read from pack_objects.err to capture stderr output for + * progress bar, and pack_objects.out to capture the pack data. */ - close(pe_pipe[1]); - close(pu_pipe[1]); while (1) { - const char *who; struct pollfd pfd[2]; - pid_t pid; - int status; - ssize_t sz; int pe, pu, pollsize; reset_timeout(); @@ -214,136 +185,104 @@ static void create_pack_file(void) pollsize = 0; pe = pu = -1; - if (0 <= pu_pipe[0]) { - pfd[pollsize].fd = pu_pipe[0]; + if (0 <= pack_objects.out) { + pfd[pollsize].fd = pack_objects.out; pfd[pollsize].events = POLLIN; pu = pollsize; pollsize++; } - if (0 <= pe_pipe[0]) { - pfd[pollsize].fd = pe_pipe[0]; + if (0 <= pack_objects.err) { + pfd[pollsize].fd = pack_objects.err; pfd[pollsize].events = POLLIN; pe = pollsize; pollsize++; } - if (pollsize) { - if (poll(pfd, pollsize, -1) < 0) { - if (errno != EINTR) { - error("poll failed, resuming: %s", - strerror(errno)); - sleep(1); - } - continue; - } - if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) { - /* Data ready; we keep the last byte - * to ourselves in case we detect - * broken rev-list, so that we can - * leave the stream corrupted. This - * is unfortunate -- unpack-objects - * would happily accept a valid pack - * data with trailing garbage, so - * appending garbage after we pass all - * the pack data is not good enough to - * signal breakage to downstream. - */ - char *cp = data; - ssize_t outsz = 0; - if (0 <= buffered) { - *cp++ = buffered; - outsz++; - } - sz = xread(pu_pipe[0], cp, - sizeof(data) - outsz); - if (0 < sz) - ; - else if (sz == 0) { - close(pu_pipe[0]); - pu_pipe[0] = -1; - } - else - goto fail; - sz += outsz; - if (1 < sz) { - buffered = data[sz-1] & 0xFF; - sz--; - } - else - buffered = -1; - sz = send_client_data(1, data, sz); - if (sz < 0) - goto fail; - } - if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) { - /* Status ready; we ship that in the side-band - * or dump to the standard error. - */ - sz = xread(pe_pipe[0], progress, - sizeof(progress)); - if (0 < sz) - send_client_data(2, progress, sz); - else if (sz == 0) { - close(pe_pipe[0]); - pe_pipe[0] = -1; - } - else - goto fail; + if (!pollsize) + break; + + if (poll(pfd, pollsize, -1) < 0) { + if (errno != EINTR) { + error("poll failed, resuming: %s", + strerror(errno)); + sleep(1); } + continue; } - - /* See if the children are still there */ - if (pid_rev_list || pid_pack_objects) { - pid = waitpid(-1, &status, WNOHANG); - if (!pid) - continue; - who = ((pid == pid_rev_list) ? "git-rev-list" : - (pid == pid_pack_objects) ? "git-pack-objects" : - NULL); - if (!who) { - if (pid < 0) { - error("git-upload-pack: %s", - strerror(errno)); - goto fail; - } - error("git-upload-pack: we weren't " - "waiting for %d", pid); - continue; + if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) { + /* Data ready; we keep the last byte to ourselves + * in case we detect broken rev-list, so that we + * can leave the stream corrupted. This is + * unfortunate -- unpack-objects would happily + * accept a valid packdata with trailing garbage, + * so appending garbage after we pass all the + * pack data is not good enough to signal + * breakage to downstream. + */ + char *cp = data; + ssize_t outsz = 0; + if (0 <= buffered) { + *cp++ = buffered; + outsz++; + } + sz = xread(pack_objects.out, cp, + sizeof(data) - outsz); + if (0 < sz) + ; + else if (sz == 0) { + close(pack_objects.out); + pack_objects.out = -1; } - if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) { - error("git-upload-pack: %s died with error.", - who); + else goto fail; + sz += outsz; + if (1 < sz) { + buffered = data[sz-1] & 0xFF; + sz--; } - if (pid == pid_rev_list) - pid_rev_list = 0; - if (pid == pid_pack_objects) - pid_pack_objects = 0; - if (pid_rev_list || pid_pack_objects) - continue; - } - - /* both died happily */ - if (pollsize) - continue; - - /* flush the data */ - if (0 <= buffered) { - data[0] = buffered; - sz = send_client_data(1, data, 1); + else + buffered = -1; + sz = send_client_data(1, data, sz); if (sz < 0) goto fail; - fprintf(stderr, "flushed.\n"); } - if (use_sideband) - packet_flush(1); - return; + if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) { + /* Status ready; we ship that in the side-band + * or dump to the standard error. + */ + sz = xread(pack_objects.err, progress, + sizeof(progress)); + if (0 < sz) + send_client_data(2, progress, sz); + else if (sz == 0) { + close(pack_objects.err); + pack_objects.err = -1; + } + else + goto fail; + } } + + if (finish_command(&pack_objects)) { + error("git-upload-pack: git-pack-objects died with error."); + goto fail; + } + if (finish_async(&rev_list)) + goto fail; /* error was already reported */ + + /* flush the data */ + if (0 <= buffered) { + data[0] = buffered; + sz = send_client_data(1, data, 1); + if (sz < 0) + goto fail; + fprintf(stderr, "flushed.\n"); + } + if (use_sideband) + packet_flush(1); + return; + fail: - if (pid_pack_objects) - kill(pid_pack_objects, SIGKILL); - if (pid_rev_list) - kill(pid_rev_list, SIGKILL); send_client_data(3, abort_msg, sizeof(abort_msg)); die("git-upload-pack: %s", abort_msg); } @@ -11,7 +11,8 @@ struct interval { }; /* auxiliary function for binary search in interval table */ -static int bisearch(ucs_char_t ucs, const struct interval *table, int max) { +static int bisearch(ucs_char_t ucs, const struct interval *table, int max) +{ int min = 0; int mid; @@ -283,7 +284,6 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) text++; } } - return w; } int is_encoding_utf8(const char *name) @@ -1,23 +1,17 @@ #include "cache.h" -#include "fetch.h" +#include "walker.h" #include "commit.h" #include "tree.h" #include "tree-walk.h" #include "tag.h" #include "blob.h" #include "refs.h" -#include "strbuf.h" -int get_tree = 0; -int get_history = 0; -int get_all = 0; -int get_verbosely = 0; -int get_recover = 0; static unsigned char current_commit_sha1[20]; -void pull_say(const char *fmt, const char *hex) +void walker_say(struct walker *walker, const char *fmt, const char *hex) { - if (get_verbosely) + if (walker->get_verbosely) fprintf(stderr, fmt, hex); } @@ -32,9 +26,9 @@ static void report_missing(const struct object *obj) sha1_to_hex(current_commit_sha1)); } -static int process(struct object *obj); +static int process(struct walker *walker, struct object *obj); -static int process_tree(struct tree *tree) +static int process_tree(struct walker *walker, struct tree *tree) { struct tree_desc desc; struct name_entry entry; @@ -59,7 +53,7 @@ static int process_tree(struct tree *tree) if (blob) obj = &blob->object; } - if (!obj || process(obj)) + if (!obj || process(walker, obj)) return -1; } free(tree->buffer); @@ -74,7 +68,7 @@ static int process_tree(struct tree *tree) static struct commit_list *complete = NULL; -static int process_commit(struct commit *commit) +static int process_commit(struct walker *walker, struct commit *commit) { if (parse_commit(commit)) return -1; @@ -88,43 +82,43 @@ static int process_commit(struct commit *commit) hashcpy(current_commit_sha1, commit->object.sha1); - pull_say("walk %s\n", sha1_to_hex(commit->object.sha1)); + walker_say(walker, "walk %s\n", sha1_to_hex(commit->object.sha1)); - if (get_tree) { - if (process(&commit->tree->object)) + if (walker->get_tree) { + if (process(walker, &commit->tree->object)) return -1; - if (!get_all) - get_tree = 0; + if (!walker->get_all) + walker->get_tree = 0; } - if (get_history) { + if (walker->get_history) { struct commit_list *parents = commit->parents; for (; parents; parents = parents->next) { - if (process(&parents->item->object)) + if (process(walker, &parents->item->object)) return -1; } } return 0; } -static int process_tag(struct tag *tag) +static int process_tag(struct walker *walker, struct tag *tag) { if (parse_tag(tag)) return -1; - return process(tag->tagged); + return process(walker, tag->tagged); } static struct object_list *process_queue = NULL; static struct object_list **process_queue_end = &process_queue; -static int process_object(struct object *obj) +static int process_object(struct walker *walker, struct object *obj) { if (obj->type == OBJ_COMMIT) { - if (process_commit((struct commit *)obj)) + if (process_commit(walker, (struct commit *)obj)) return -1; return 0; } if (obj->type == OBJ_TREE) { - if (process_tree((struct tree *)obj)) + if (process_tree(walker, (struct tree *)obj)) return -1; return 0; } @@ -132,7 +126,7 @@ static int process_object(struct object *obj) return 0; } if (obj->type == OBJ_TAG) { - if (process_tag((struct tag *)obj)) + if (process_tag(walker, (struct tag *)obj)) return -1; return 0; } @@ -141,7 +135,7 @@ static int process_object(struct object *obj) typename(obj->type), sha1_to_hex(obj->sha1)); } -static int process(struct object *obj) +static int process(struct walker *walker, struct object *obj) { if (obj->flags & SEEN) return 0; @@ -154,7 +148,7 @@ static int process(struct object *obj) else { if (obj->flags & COMPLETE) return 0; - prefetch(obj->sha1); + walker->prefetch(walker, obj->sha1); } object_list_insert(obj, process_queue_end); @@ -162,7 +156,7 @@ static int process(struct object *obj) return 0; } -static int loop(void) +static int loop(struct walker *walker) { struct object_list *elem; @@ -178,25 +172,25 @@ static int loop(void) * the queue because we needed to fetch it first. */ if (! (obj->flags & TO_SCAN)) { - if (fetch(obj->sha1)) { + if (walker->fetch(walker, obj->sha1)) { report_missing(obj); return -1; } } if (!obj->type) parse_object(obj->sha1); - if (process_object(obj)) + if (process_object(walker, obj)) return -1; } return 0; } -static int interpret_target(char *target, unsigned char *sha1) +static int interpret_target(struct walker *walker, char *target, unsigned char *sha1) { if (!get_sha1_hex(target, sha1)) return 0; if (!check_ref_format(target)) { - if (!fetch_ref(target, sha1)) { + if (!walker->fetch_ref(walker, target, sha1)) { return 0; } } @@ -213,18 +207,17 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, return 0; } -int pull_targets_stdin(char ***target, const char ***write_ref) +int walker_targets_stdin(char ***target, const char ***write_ref) { int targets = 0, targets_alloc = 0; struct strbuf buf; *target = NULL; *write_ref = NULL; - strbuf_init(&buf); + strbuf_init(&buf, 0); while (1) { char *rf_one = NULL; char *tg_one; - read_line(&buf, stdin, '\n'); - if (buf.eof) + if (strbuf_getline(&buf, stdin, '\n') == EOF) break; tg_one = buf.buf; rf_one = strchr(tg_one, '\t'); @@ -240,10 +233,11 @@ int pull_targets_stdin(char ***target, const char ***write_ref) (*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL; targets++; } + strbuf_release(&buf); return targets; } -void pull_targets_free(int targets, char **target, const char **write_ref) +void walker_targets_free(int targets, char **target, const char **write_ref) { while (targets--) { free(target[targets]); @@ -252,8 +246,8 @@ void pull_targets_free(int targets, char **target, const char **write_ref) } } -int pull(int targets, char **target, const char **write_ref, - const char *write_ref_log_details) +int walker_fetch(struct walker *walker, int targets, char **target, + const char **write_ref, const char *write_ref_log_details) { struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *)); unsigned char *sha1 = xmalloc(targets * 20); @@ -275,19 +269,19 @@ int pull(int targets, char **target, const char **write_ref, } } - if (!get_recover) + if (!walker->get_recover) for_each_ref(mark_complete, NULL); for (i = 0; i < targets; i++) { - if (interpret_target(target[i], &sha1[20 * i])) { + if (interpret_target(walker, target[i], &sha1[20 * i])) { error("Could not interpret %s as something to pull", target[i]); goto unlock_and_fail; } - if (process(lookup_unknown_object(&sha1[20 * i]))) + if (process(walker, lookup_unknown_object(&sha1[20 * i]))) goto unlock_and_fail; } - if (loop()) + if (loop(walker)) goto unlock_and_fail; if (write_ref_log_details) { @@ -308,10 +302,16 @@ int pull(int targets, char **target, const char **write_ref, return 0; - unlock_and_fail: for (i = 0; i < targets; i++) if (lock[i]) unlock_ref(lock[i]); + return -1; } + +void walker_free(struct walker *walker) +{ + walker->cleanup(walker); + free(walker); +} diff --git a/walker.h b/walker.h new file mode 100644 index 0000000000..ea2c363f4e --- /dev/null +++ b/walker.h @@ -0,0 +1,37 @@ +#ifndef WALKER_H +#define WALKER_H + +struct walker { + void *data; + int (*fetch_ref)(struct walker *, char *ref, unsigned char *sha1); + void (*prefetch)(struct walker *, unsigned char *sha1); + int (*fetch)(struct walker *, unsigned char *sha1); + void (*cleanup)(struct walker *); + int get_tree; + int get_history; + int get_all; + int get_verbosely; + int get_recover; + + int corrupt_object_found; +}; + +/* Report what we got under get_verbosely */ +void walker_say(struct walker *walker, const char *, const char *); + +/* Load pull targets from stdin */ +int walker_targets_stdin(char ***target, const char ***write_ref); + +/* Free up loaded targets */ +void walker_targets_free(int targets, char **target, const char **write_ref); + +/* If write_ref is set, the ref filename to write the target value to. */ +/* If write_ref_log_details is set, additional text will appear in the ref log. */ +int walker_fetch(struct walker *impl, int targets, char **target, + const char **write_ref, const char *write_ref_log_details); + +void walker_free(struct walker *walker); + +struct walker *get_http_walker(const char *url); + +#endif /* WALKER_H */ diff --git a/wt-status.c b/wt-status.c index 58dd716a4e..9a6ef4a89a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -51,31 +51,34 @@ void wt_status_prepare(struct wt_status *s) head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; + s->fp = stdout; + s->index_file = get_index_file(); } -static void wt_status_print_cached_header(const char *reference) +static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# Changes to be committed:"); - if (reference) { - color_printf_ln(c, "# (use \"git reset %s <file>...\" to unstage)", reference); + color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (s->reference) { + color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { - color_printf_ln(c, "# (use \"git rm --cached <file>...\" to unstage)"); + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); } - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_header(const char *main, const char *sub) +static void wt_status_print_header(struct wt_status *s, + const char *main, const char *sub) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# %s:", main); - color_printf_ln(c, "# (%s)", sub); - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "# %s:", main); + color_fprintf_ln(s->fp, c, "# (%s)", sub); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_trailer(void) +static void wt_status_print_trailer(struct wt_status *s) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } static const char *quote_crlf(const char *in, char *buf, size_t sz) @@ -107,7 +110,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz) return ret; } -static void wt_status_print_filepair(int t, struct diff_filepair *p) +static void wt_status_print_filepair(struct wt_status *s, + int t, struct diff_filepair *p) { const char *c = color(t); const char *one, *two; @@ -116,36 +120,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) one = quote_crlf(p->one->path, onebuf, sizeof(onebuf)); two = quote_crlf(p->two->path, twobuf, sizeof(twobuf)); - color_printf(color(WT_STATUS_HEADER), "#\t"); + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", one); + color_fprintf(s->fp, c, "new file: %s", one); break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", one, two); + color_fprintf(s->fp, c, "copied: %s -> %s", one, two); break; case DIFF_STATUS_DELETED: - color_printf(c, "deleted: %s", one); + color_fprintf(s->fp, c, "deleted: %s", one); break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", one); + color_fprintf(s->fp, c, "modified: %s", one); break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", one, two); + color_fprintf(s->fp, c, "renamed: %s -> %s", one, two); break; case DIFF_STATUS_TYPE_CHANGED: - color_printf(c, "typechange: %s", one); + color_fprintf(s->fp, c, "typechange: %s", one); break; case DIFF_STATUS_UNKNOWN: - color_printf(c, "unknown: %s", one); + color_fprintf(s->fp, c, "unknown: %s", one); break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", one); + color_fprintf(s->fp, c, "unmerged: %s", one); break; default: die("bug: unhandled diff status %c", p->status); } - printf("\n"); + fprintf(s->fp, "\n"); } static void wt_status_print_updated_cb(struct diff_queue_struct *q, @@ -159,14 +163,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q, if (q->queue[i]->status == 'U') continue; if (!shown_header) { - wt_status_print_cached_header(s->reference); + wt_status_print_cached_header(s); s->commitable = 1; shown_header = 1; } - wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); } if (shown_header) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_changed_cb(struct diff_queue_struct *q, @@ -183,18 +187,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, msg = use_add_rm_msg; break; } - wt_status_print_header("Changed but not updated", msg); + wt_status_print_header(s, "Changed but not updated", msg); } for (i = 0; i < q->nr; i++) - wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]); if (q->nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_read_cache(struct wt_status *s) { discard_cache(); - read_cache(); + read_cache_from(s->index_file); } static void wt_status_print_initial(struct wt_status *s) @@ -205,16 +209,16 @@ static void wt_status_print_initial(struct wt_status *s) wt_read_cache(s); if (active_nr) { s->commitable = 1; - wt_status_print_cached_header(NULL); + wt_status_print_cached_header(s); } for (i = 0; i < active_nr; i++) { - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s", quote_crlf(active_cache[i]->name, buf, sizeof(buf))); } if (active_nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_updated(struct wt_status *s) @@ -275,12 +279,12 @@ static void wt_status_print_untracked(struct wt_status *s) } if (!shown_header) { s->workdir_untracked = 1; - wt_status_print_header("Untracked files", + wt_status_print_header(s, "Untracked files", use_add_to_include_msg); shown_header = 1; } - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s", ent->len, ent->name); } } @@ -310,14 +314,14 @@ void wt_status_print(struct wt_status *s) branch_name = ""; on_what = "Not currently on any branch."; } - color_printf_ln(color(WT_STATUS_HEADER), + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# %s%s", on_what, branch_name); } if (s->is_initial) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); - color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); wt_status_print_initial(s); } else { @@ -331,7 +335,7 @@ void wt_status_print(struct wt_status *s) wt_status_print_verbose(s); if (!s->commitable) { if (s->amend) - printf("# No changes\n"); + fprintf(s->fp, "# No changes\n"); else if (s->workdir_dirty) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); else if (s->workdir_untracked) diff --git a/wt-status.h b/wt-status.h index cfea4ae688..77449326db 100644 --- a/wt-status.h +++ b/wt-status.h @@ -1,6 +1,8 @@ #ifndef STATUS_H #define STATUS_H +#include <stdio.h> + enum color_wt_status { WT_STATUS_HEADER, WT_STATUS_UPDATED, @@ -19,6 +21,8 @@ struct wt_status { int commitable; int workdir_dirty; int workdir_untracked; + const char *index_file; + FILE *fp; }; int git_status_config(const char *var, const char *value); diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 5cb7171a8f..1bad8462fb 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -257,8 +257,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, return ec; } } - - return -1; } diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 2ade97b257..d7974d1a3e 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -232,8 +232,6 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) return i1 >= s1 && i2 >= s2; } else return s1 == s2 && !memcmp(l1, l2, s1); - - return 0; } static unsigned long xdl_hash_record_with_whitespace(char const **data, |