diff options
157 files changed, 6002 insertions, 2405 deletions
diff --git a/Documentation/RelNotes-1.5.4.4.txt b/Documentation/RelNotes-1.5.4.4.txt index 5635977c93..89fa6d03bc 100644 --- a/Documentation/RelNotes-1.5.4.4.txt +++ b/Documentation/RelNotes-1.5.4.4.txt @@ -37,10 +37,30 @@ Fixes since v1.5.4.3 * "git revert" did not properly fail when attempting to run with a dirty index. -Also included are a handful documentation updates. + * "git merge --no-commit --no-ff <other>" incorrectly made commits. + + * "git merge --squash --no-ff <other>", which is a nonsense combination + of options, was not rejected. + + * "git ls-remote" and "git remote show" against an empty repository + failed, instead of just giving an empty result (regression). + + * "git fast-import" did not handle a renamed path whose name needs to be + quoted, due to a bug in unquote_c_style() function. + + * "git cvsexportcommit" was confused when multiple files with the same + basename needed to be pushed out in the same commit. + + * "git daemon" did not send early errors to syslog. ---- -exec >/var/tmp/1 -echo O=$(git describe maint) -O=v1.5.4.3-32-g0f2d447 -git shortlog --no-merges $O..maint + * "git log --merge" did not work well with --left-right option. + + * "git svn" promprted for client cert password every time it accessed the + server. + + * The reset command in "git fast-import" data stream was documented to + end with an optional LF, but it actually required one. + + * "git svn dcommit/rebase" did not honor --rewrite-root option. + +Also included are a handful documentation updates. diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt index b57fa1eb1a..874dad9a4f 100644 --- a/Documentation/RelNotes-1.5.5.txt +++ b/Documentation/RelNotes-1.5.5.txt @@ -160,13 +160,9 @@ Fixes since v1.5.4 All of the fixes in v1.5.4 maintenance series are included in this release, unless otherwise noted. - * "git-daemon" did not send early errors to syslog. - * "git-http-push" did not allow deletion of remote ref with the usual "push <remote> :<branch>" syntax. - * "git-log --merge" did not well work with --left-right option. - * "git-rebase --abort" did not go back to the right location if "git-reset" was run during the "git-rebase" session. diff --git a/Documentation/config.txt b/Documentation/config.txt index 2091caa111..0865f4e01a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -420,6 +420,11 @@ branch.<name>.rebase:: it unless you understand the implications (see linkgit:git-rebase[1] for details). +browser.<tool>.cmd:: + Specify the command to invoke the specified browser. The + specified command is evaluated in shell with the URLs passed + as arguments. (See linkgit:git-web--browse[1].) + browser.<tool>.path:: Override the path for the given tool that may be used to browse HTML help (see '-w' option in linkgit:git-help[1]) or a @@ -556,6 +561,11 @@ format.suffix:: `.patch`. Use this variable to change that suffix (make sure to include the dot if you want it). +format.pretty:: + The default pretty format for log/show/whatchanged command, + See linkgit:git-log[1], linkgit:git-show[1], + linkgit:git-whatchanged[1]. + gc.aggressiveWindow:: The window size parameter used in the delta compression algorithm used by 'git gc --aggressive'. This defaults @@ -585,6 +595,10 @@ gc.packrefs:: at some stage, and setting this to `false` will continue to prevent `git pack-refs` from being run from `git gc`. +gc.pruneexpire:: + When `git gc` is run, it will call `prune --expire 2.weeks.ago`. + Override the grace period with this config variable. + gc.reflogexpire:: `git reflog expire` removes reflog entries older than this time; defaults to 90 days. @@ -743,14 +757,20 @@ log.showroot:: Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which normally hide the root commit will now show it. True by default. +man.viewer:: + Specify the programs that may be used to display help in the + 'man' format. See linkgit:git-help[1]. + merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. merge.tool:: Controls which merge resolution program is used by - linkgit:git-mergetool[1]. Valid values are: "kdiff3", "tkdiff", - "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff". + linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", + "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and + "opendiff". Any other value is treated is custom merge tool + and there must be a corresponing mergetool.<tool>.cmd option. merge.verbosity:: Controls the amount of output shown by the recursive merge @@ -777,6 +797,31 @@ mergetool.<tool>.path:: Override the path for the given tool. This is useful in case your tool is not in the PATH. +mergetool.<tool>.cmd:: + Specify the command to invoke the specified merge tool. The + specified command is evaluated in shell with the following + variables available: 'BASE' is the name of a temporary file + containing the common base of the files to be merged, if available; + 'LOCAL' is the name of a temporary file containing the contents of + the file on the current branch; 'REMOTE' is the name of a temporary + file containing the contents of the file from the branch being + merged; 'MERGED' contains the name of the file to which the merge + tool should write the results of a successful merge. + +mergetool.<tool>.trustExitCode:: + For a custom merge command, specify whether the exit code of + the merge command can be used to determine whether the merge was + successful. If this is not set to true then the merge target file + timestamp is checked and the merge assumed to have been successful + if the file has been updated, otherwise the user is prompted to + indicate the success of the merge. + +mergetool.keepBackup:: + After performing a merge, the original file with conflict markers + can be saved as a file with a `.orig` extension. If this variable + is set to `false` then this file is not preserved. Defaults to + `true` (i.e. keep the backup files). + pack.window:: The size of the window used by linkgit:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. @@ -828,7 +873,7 @@ pack.indexVersion:: whenever the corresponding pack is larger than 2 GB. Otherwise the default is 1. -pack.packSizeLimit: +pack.packSizeLimit:: The default maximum size of a pack. This setting only affects packing to a file, i.e. the git:// protocol is unaffected. It can be overridden by the `\--max-pack-size` option of @@ -864,15 +909,15 @@ remote.<name>.skipDefaultUpdate:: remote.<name>.receivepack:: The default program to execute on the remote side when pushing. See - option \--exec of linkgit:git-push[1]. + option \--receive-pack of linkgit:git-push[1]. remote.<name>.uploadpack:: The default program to execute on the remote side when fetching. See - option \--exec of linkgit:git-fetch-pack[1]. + option \--upload-pack of linkgit:git-fetch-pack[1]. remote.<name>.tagopt:: - Setting this value to --no-tags disables automatic tag following when fetching - from remote <name> + Setting this value to \--no-tags disables automatic tag following when + fetching from remote <name> remotes.<group>:: The list of remotes which are fetched by "git remote update @@ -939,12 +984,6 @@ imap:: The configuration variables in the 'imap' section are described in linkgit:git-imap-send[1]. -receive.fsckObjects:: - If it is set to true, git-receive-pack will check all received - objects. It will abort in the case of a malformed object or a - broken link. The result of an abort are only dangling objects. - The default value is true. - receive.unpackLimit:: If the number of objects received in a push is below this limit then the objects will be unpacked into loose object diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 47799097ce..c751a17d07 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -207,16 +207,14 @@ patch:: and the working tree file and asks you if you want to stage the change of each hunk. You can say: - y - add the change from that hunk to index - n - do not add the change from that hunk to index - a - add the change from that hunk and all the rest to index - d - do not the change from that hunk nor any of the rest to index - j - do not decide on this hunk now, and view the next - undecided hunk - J - do not decide on this hunk now, and view the next hunk - k - do not decide on this hunk now, and view the previous - undecided hunk - K - do not decide on this hunk now, and view the previous hunk + y - stage this hunk + n - do not stage this hunk + a - stage this and all the remaining hunks in the file + d - do not stage this hunk nor any of the remaining hunks in the file + j - leave this hunk undecided, see next undecided hunk + J - leave this hunk undecided, see next hunk + k - leave this hunk undecided, see previous undecided hunk + K - leave this hunk undecided, see previous hunk s - split the current hunk into smaller hunks ? - print help + diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index e640fc75cd..2387a8d6c2 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -9,7 +9,7 @@ git-am - Apply a series of patches from a mailbox SYNOPSIS -------- [verse] -'git-am' [--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8] +'git-am' [--signoff] [--keep] [--utf8 | --no-utf8] [--3way] [--interactive] [--binary] [--whitespace=<option>] [-C<n>] [-p<n>] <mbox>|<Maildir>... @@ -32,10 +32,6 @@ OPTIONS Add `Signed-off-by:` line to the commit message, using the committer identity of yourself. --d=<dir>, --dotest=<dir>:: - Instead of `.dotest` directory, use <dir> as a working - area to store extracted patches. - -k, --keep:: Pass `-k` flag to `git-mailinfo` (see linkgit:git-mailinfo[1]). diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 2b8ffe5324..57598eb056 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -8,7 +8,7 @@ git-fetch-pack - Receive missing objects from another repository SYNOPSIS -------- -'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...] +'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...] DESCRIPTION ----------- @@ -45,6 +45,12 @@ OPTIONS Spend extra cycles to minimize the number of objects to be sent. Use it on slower connection. +\--include-tag:: + If the remote side supports it, annotated tags objects will + be downloaded on the same connection as the other objects if + the object the tag references is downloaded. The caller must + otherwise determine the tags this option made available. + \--upload-pack=<git-upload-pack>:: Use this to specify the path to 'git-upload-pack' on the remote side, if is not found on your $PATH. diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 2e7be916aa..229a7c9b30 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] [--auto] [--quiet] +'git-gc' [--aggressive] [--auto] [--quiet] DESCRIPTION ----------- @@ -25,17 +25,6 @@ operating performance. Some git commands may automatically run OPTIONS ------- ---prune:: - Usually `git-gc` packs refs, expires old reflog entries, - packs loose objects, - and removes old 'rerere' records. Removal - of unreferenced loose objects is an unsafe operation - while other git operations are in progress, so it is not - done by default. Pass this option if you want it, and only - when you know nobody else is creating new objects in the - repository at the same time (e.g. never use this option - in a cron script). - --aggressive:: Usually 'git-gc' runs very quickly while providing good disk space utilization and performance. This option will cause @@ -104,6 +93,10 @@ the value, the more time is spent optimizing the delta compression. See the documentation for the --window' option in linkgit:git-repack[1] for more details. This defaults to 10. +The optional configuration variable 'gc.pruneExpire' controls how old +the unreferenced loose objects have to be before they are pruned. The +default is "2 weeks ago". + See Also -------- linkgit:git-prune[1] diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index 0926dc12ba..be2ae53b90 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -33,17 +33,21 @@ OPTIONS option supersedes any other option. -i|--info:: - Use the 'info' program to display the manual page, instead of - the 'man' program that is used by default. + Display manual page for the command in the 'info' format. The + 'info' program will be used for that purpose. -m|--man:: - Use the 'man' program to display the manual page. This may be - used to override a value set in the 'help.format' - configuration variable. + Display manual page for the command in the 'man' format. This + option may be used to override a value set in the + 'help.format' configuration variable. ++ +By default the 'man' program will be used to display the manual page, +but the 'man.viewer' configuration variable may be used to choose +other display programs (see below). -w|--web:: - Use a web browser to display the HTML manual page, instead of - the 'man' program that is used by default. + Display manual page for the command in the 'web' (HTML) + format. A web browser will be used for that purpose. + The web browser can be specified using the configuration variable 'help.browser', or 'web.browser' if the former is not set. If none of @@ -54,6 +58,9 @@ linkgit:git-web--browse[1] for more information about this. CONFIGURATION VARIABLES ----------------------- +help.format +~~~~~~~~~~~ + If no command line option is passed, the 'help.format' configuration variable will be checked. The following values are supported for this variable; they make 'git-help' behave as their corresponding command @@ -61,15 +68,47 @@ line option: * "man" corresponds to '-m|--man', * "info" corresponds to '-i|--info', -* "web" or "html" correspond to '-w|--web', +* "web" or "html" correspond to '-w|--web'. + +help.browser, web.browser and browser.<tool>.path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also be checked if the 'web' format is chosen (either by command line option or configuration variable). See '-w|--web' in the OPTIONS section above and linkgit:git-web--browse[1]. -Note that these configuration variables should probably be set using -the '--global' flag, for example like this: +man.viewer +~~~~~~~~~~ + +The 'man.viewer' config variable will be checked if the 'man' format +is chosen. Only the following values are currently supported: + +* "man": use the 'man' program as usual, +* "woman": use 'emacsclient' to launch the "woman" mode in emacs +(this only works starting with emacsclient versions 22), +* "konqueror": use a man KIO slave in konqueror. + +Multiple values may be given to this configuration variable. Their +corresponding programs will be tried in the order listed in the +configuration file. + +For example, this configuration: + + [man] + viewer = konqueror + viewer = woman + +will try to use konqueror first. But this may fail (for example if +DISPLAY is not set) and in that case emacs' woman mode will be tried. + +If everything fails the 'man' program will be tried anyway. + +Note about git config --global +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Note that all these configuration variables should probably be set +using the '--global' flag, for example like this: ------------------------------------------------ $ git config --global help.format web diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 0c9ad7f2bb..c136b10692 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -68,7 +68,8 @@ HOW MERGE WORKS --------------- A merge is always between the current `HEAD` and one or more -remote branch heads, and the index file must exactly match the +commits (usually, branch head or tag), and the index file must +exactly match the tree of `HEAD` commit (i.e. the contents of the last commit) when it happens. In other words, `git-diff --cached HEAD` must report no changes. diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 50f106ec5b..8ed44947ef 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -12,12 +12,12 @@ SYNOPSIS DESCRIPTION ----------- -Use 'git mergetool' to run one of several merge utilities to resolve +Use `git mergetool` to run one of several merge utilities to resolve merge conflicts. It is typically run after linkgit:git-merge[1]. If one or more <file> parameters are given, the merge tool program will be run to resolve differences on each file. If no <file> names are -specified, 'git mergetool' will run the merge tool program on every file +specified, `git mergetool` will run the merge tool program on every file with merge conflicts. OPTIONS @@ -27,16 +27,38 @@ OPTIONS Valid merge tools are: 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' +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 +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. +`mergetool.kdiff3.path`. Otherwise, `git mergetool` assumes the +tool is available in PATH. ++ +Instead of running one of the known merge tool programs +`git mergetool` can be customized to run an alternative program +by specifying the command line to invoke in a configration +variable `mergetool.<tool>.cmd`. ++ +When `git mergetool` is invoked with this tool (either through the +`-t` or `--tool` option or the `merge.tool` configuration +variable) the configured command line will be invoked with `$BASE` +set to the name of a temporary file containing the common base for +the merge, if available; `$LOCAL` set to the name of a temporary +file containing the contents of the file on the current branch; +`$REMOTE` set to the name of a temporary file containing the +contents of the file to be merged, and `$MERGED` set to the name +of the file to which the merge tool should write the result of the +merge resolution. ++ +If the custom merge tool correctly indicates the success of a +merge resolution with its exit code then the configuration +variable `mergetool.<tool>.trustExitCode` can be set to `true`. +Otherwise, `git mergetool` will prompt the user to indicate the +success of the resolution after the custom tool has exited. Author ------ diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 5c1bd3b081..eed0a94c6e 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -73,6 +73,11 @@ base-name:: as if all refs under `$GIT_DIR/refs` are specified to be included. +--include-tag:: + Include unasked-for annotated tags if the object they + reference was included in the resulting packfile. This + can be useful to send new tags to native git clients. + --window=[N], --depth=[N]:: These two options affect how the objects contained in the pack are stored using delta compression. The diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 737894390d..3405ca09e8 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -21,6 +21,8 @@ Note that you can use `.` (current directory) as the <repository> to pull from the local repository -- this is useful when merging local branches into the current branch. +Also note that options meant for `git-pull` itself and underlying +`git-merge` must be given before the options meant for `git-fetch`. OPTIONS ------- diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4b10304740..e0412e0866 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -262,8 +262,7 @@ hook if one exists. You can use this hook to do sanity checks and reject the rebase if it isn't appropriate. Please see the template pre-rebase hook script for an example. -You must be in the top directory of your project to start (or continue) -a rebase. Upon completion, <branch> will be the current branch. +Upon completion, <branch> will be the current branch. INTERACTIVE MODE ---------------- diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index f9bba36c23..047e3ce14d 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -19,6 +19,8 @@ depending on the subcommand: git reflog expire [--dry-run] [--stale-fix] [--verbose] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>... +git reflog delete ref@\{specifier\}... + git reflog [show] [log-options] [<ref>] Reflog is a mechanism to record when the tip of branches are @@ -43,6 +45,9 @@ two moves ago", `master@\{one.week.ago\}` means "where master used to point to one week ago", and so on. See linkgit:git-rev-parse[1] for more details. +To delete single entries from the reflog, use the subcommand "delete" +and specify the _exact_ entry (e.g. ``git reflog delete master@\{2\}''). + OPTIONS ------- @@ -75,6 +80,15 @@ them. --all:: Instead of listing <refs> explicitly, prune all refs. +--updateref:: + Update the ref with the sha1 of the top reflog entry (i.e. + <ref>@\{0\}) after expiring or deleting. + +--rewrite:: + While expiring or deleting, adjust each reflog entry to ensure + that the `old` sha1 field points to the `new` sha1 field of the + previous entry. + --verbose:: Print extra information on screen. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index f02f6bbb49..6513c2efe1 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -325,7 +325,7 @@ The lines after the separator describe the options. Each line of options has this format: ------------ -<opt_spec><arg_spec>? SP+ help LF +<opt_spec><flags>* SP+ help LF ------------ `<opt_spec>`:: @@ -334,10 +334,17 @@ Each line of options has this format: 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). +`<flags>`:: + `<flags>` are of `*`, `=`, `?` or `!`. + * Use `=` if the option takes an argument. + + * Use `?` to mean that the option is optional (though its use is discouraged). + + * Use `*` to mean that this option should not be listed in the usage + generated for the `-h` argument. It's shown for `--help-all` as + documented in linkgit:gitcli[5]. + + * Use `!` to not make the corresponding negated long option available. The remainder of the line, after stripping the spaces, is used as the help associated to the option. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 48e6f5a3f7..8dc35d493e 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -8,7 +8,7 @@ git-stash - Stash the changes in a dirty working directory away SYNOPSIS -------- [verse] -'git-stash' (list | show [<stash>] | apply [<stash>] | clear) +'git-stash' (list | show [<stash>] | apply [<stash>] | clear | drop [<stash>] | pop [<stash>]) 'git-stash' [save [<message>]] DESCRIPTION @@ -85,6 +85,17 @@ clear:: Remove all the stashed states. Note that those states will then be subject to pruning, and may be difficult or impossible to recover. +drop [<stash>]:: + + Remove a single stashed state from the stash list. When no `<stash>` + is given, it removes the latest one. i.e. `stash@\{0}` + +pop [<stash>]:: + + Remove a single stashed state from the stash list and apply on top + of the current working tree state. When no `<stash>` is given, + `stash@\{0}` is assumed. See also `apply`. + DISCUSSION ---------- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index e96bf36521..41f9f63566 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -19,8 +19,9 @@ COMMANDS -------- add:: Add the given repository as a submodule at the given path - to the changeset to be committed next. In particular, the - repository is cloned at the specified path, added to the + to the changeset to be committed next. If path is a valid + repository within the project, it is added as is. Otherwise, + repository is cloned at the specified path. path is 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 diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 3697896a06..b79be3fd4c 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -40,9 +40,6 @@ OPTIONS and make the best effort to recover as many objects as possible. ---strict:: - Don't write objects with broken content or links. - Author ------ diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index df57d010e5..ddbae5b194 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -27,6 +27,8 @@ The following browsers (or commands) are currently supported: * dillo * open (this is the default under Mac OS X GUI) +Custom commands may also be specified. + OPTIONS ------- -b BROWSER|--browser=BROWSER:: @@ -43,16 +45,35 @@ OPTIONS CONFIGURATION VARIABLES ----------------------- +CONF.VAR (from -c option) and web.browser +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The web browser can be specified using a configuration variable passed with the -c (or --config) command line option, or the 'web.browser' configuration variable if the former is not used. +browser.<tool>.path +~~~~~~~~~~~~~~~~~~~ + You can explicitly provide a full path to your preferred browser by setting the configuration variable 'browser.<tool>.path'. For example, you can configure the absolute path to firefox by setting 'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool is available in PATH. +browser.<tool>.cmd +~~~~~~~~~~~~~~~~~~ + +When the browser, specified by options or configuration variables, is +not among the supported ones, then the corresponding +'browser.<tool>.cmd' configuration variable will be looked up. If this +variable exists then "git web--browse" will treat the specified tool +as a custom command and will use a shell eval to run the command with +the URLs passed as arguments. + +Note about git config --global +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Note that these configuration variables should probably be set using the '--global' flag, for example like this: diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt index 54947b6769..a6e7bd4c8b 100644 --- a/Documentation/git-whatchanged.txt +++ b/Documentation/git-whatchanged.txt @@ -38,11 +38,6 @@ OPTIONS Show git internal diff output, but for the whole tree, not just the top level. ---pretty=<format>:: - Controls the output format for the commit logs. - <format> can be one of 'raw', 'medium', 'short', 'full', - and 'oneline'. - -m:: By default, differences for merge commits are not shown. With this flag, show differences to that commit from all @@ -51,6 +46,10 @@ OPTIONS However, it is not very useful in general, although it *is* useful on a file-by-file basis. +include::pretty-options.txt[] + +include::pretty-formats.txt[] + Examples -------- git-whatchanged -p v2.6.12.. include/scsi drivers/scsi:: diff --git a/Documentation/git.txt b/Documentation/git.txt index 741ae0e4c8..3ed24d449a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.5.4.3/git.html[documentation for release 1.5.4.3] +* link:v1.5.4.4/git.html[documentation for release 1.5.4.4] * release notes for + link:RelNotes-1.5.4.4.txt[1.5.4.4], link:RelNotes-1.5.4.3.txt[1.5.4.3], link:RelNotes-1.5.4.2.txt[1.5.4.2], link:RelNotes-1.5.4.1.txt[1.5.4.1], diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl index fe3cd72d6f..4065a3a27a 100644 --- a/Documentation/manpage-1.72.xsl +++ b/Documentation/manpage-1.72.xsl @@ -1,5 +1,9 @@ -<!-- callout.xsl: converts asciidoc callouts to man page format --> +<!-- Based on callouts.xsl. Fixes man page callouts for DocBook 1.72 XSL --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<xsl:param name="man.output.quietly" select="1"/> +<xsl:param name="refentry.meta.get.quietly" select="1"/> + <xsl:template match="co"> <xsl:value-of select="concat('▓fB(',substring-after(@id,'-'),')▓fR')"/> </xsl:template> diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 973d8dd733..6d66c74cc1 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -4,6 +4,9 @@ where '<format>' can be one of 'oneline', 'short', 'medium', 'full', 'fuller', 'email', 'raw' and 'format:<string>'. When omitted, the format defaults to 'medium'. ++ +Note: you can specify the default pretty format in the repository +configuration (see linkgit:git-config[1]). --abbrev-commit:: Instead of showing the full 40-byte hexadecimal commit object diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index fde3b45321..c364a22c8f 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -111,9 +111,10 @@ stderr as follows: .no_stdin, .no_stdout, .no_stderr: The respective channel is redirected to /dev/null. - .stdout_to_stderr: stdout of the child is redirected to the - parent's stderr (i.e. *not* to what .err or - .no_stderr specify). + .stdout_to_stderr: stdout of the child is redirected to its + stderr. This happens after stderr is itself redirected. + So stdout will follow stderr to wherever it is + redirected. To modify the environment of the sub-process, specify an array of string pointers (NULL terminated) in .env: @@ -3,6 +3,10 @@ all:: # Define V=1 to have a more verbose compile. # +# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf() +# or vsnprintf() return -1 instead of number of characters which would +# have been written to the final string if enough space had been available. +# # Define FREAD_READS_DIRECTORIES if your are on a system which succeeds # when attempting to read from an fopen'ed directory. # @@ -243,7 +247,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-cvsexportcommit.perl \ + git-cvsserver.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -304,7 +308,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 parse-options.h transport.h diffcore.h hash.h fsck.h \ + mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h ll-merge.h fsck.h \ pack-revindex.h DIFF_OBJS = \ @@ -329,7 +333,7 @@ LIB_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 \ transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \ - alias.o fsck.o pack-revindex.o + ll-merge.o alias.o fsck.o pack-revindex.o BUILTIN_OBJS = \ builtin-add.o \ @@ -381,6 +385,7 @@ BUILTIN_OBJS = \ builtin-push.o \ builtin-read-tree.o \ builtin-reflog.o \ + builtin-remote.o \ builtin-send-pack.o \ builtin-config.o \ builtin-rerere.o \ @@ -478,6 +483,7 @@ ifeq ($(uname_S),FreeBSD) NO_MEMMEM = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -629,6 +635,10 @@ endif ifdef NO_C99_FORMAT BASIC_CFLAGS += -DNO_C99_FORMAT endif +ifdef SNPRINTF_RETURNS_BOGUS + COMPAT_CFLAGS += -DSNPRINTF_RETURNS_BOGUS + COMPAT_OBJS += compat/snprintf.o +endif ifdef FREAD_READS_DIRECTORIES COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES COMPAT_OBJS += compat/fopen.o @@ -747,6 +757,9 @@ ifdef THREADED_DELTA_SEARCH EXTLIBS += -lpthread LIB_OBJS += thread-utils.o endif +ifdef DIR_HAS_BSD_GROUP_SEMANTICS + COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS +endif ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks diff --git a/builtin-add.c b/builtin-add.c index 820110e085..4a91e3eb11 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -228,18 +228,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) goto finish; } - if (*argv) { - /* Was there an invalid path? */ - if (pathspec) { - int num; - for (num = 0; pathspec[num]; num++) - ; /* just counting */ - if (argc != num) - exit(1); /* error message already given */ - } else - exit(1); /* error message already given */ - } - fill_directory(&dir, pathspec, ignored_too); if (show_only) { diff --git a/builtin-checkout.c b/builtin-checkout.c index 6b08016228..7deb504837 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -152,6 +152,7 @@ static int reset_to_new(struct tree *tree, int quiet) { struct unpack_trees_options opts; struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; opts.update = 1; @@ -159,6 +160,8 @@ static int reset_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; + opts.src_index = &the_index; + opts.dst_index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -170,6 +173,7 @@ static void reset_clean_to_new(struct tree *tree, int quiet) { struct unpack_trees_options opts; struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; opts.skip_unmerged = 1; @@ -177,6 +181,8 @@ static void reset_clean_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; + opts.src_index = &the_index; + opts.dst_index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -224,8 +230,11 @@ static int merge_working_tree(struct checkout_opts *opts, struct tree_desc trees[2]; struct tree *tree; struct unpack_trees_options topts; + memset(&topts, 0, sizeof(topts)); topts.head_idx = -1; + topts.src_index = &the_index; + topts.dst_index = &the_index; refresh_cache(REFRESH_QUIET); diff --git a/builtin-clean.c b/builtin-clean.c index 3b220d5060..fefec3010c 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -10,6 +10,7 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" +#include "quote.h" static int force = -1; /* unset */ @@ -34,7 +35,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct dir_struct dir; const char *path, *base; static const char **pathspec; - int prefix_offset = 0; + struct strbuf buf; + const char *qname; char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet), @@ -56,6 +58,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, options, builtin_clean_usage, 0); + strbuf_init(&buf, 0); memset(&dir, 0, sizeof(dir)); if (ignored_only) dir.show_ignored = 1; @@ -72,8 +75,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!ignored) setup_standard_excludes(&dir); - if (prefix) - prefix_offset = strlen(prefix); pathspec = get_pathspec(prefix, argv); read_cache(); @@ -134,39 +135,34 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (S_ISDIR(st.st_mode)) { strbuf_addstr(&directory, ent->name); + qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); if (show_only && (remove_directories || matches)) { - printf("Would remove %s\n", - directory.buf + prefix_offset); + printf("Would remove %s\n", qname); } else if (remove_directories || matches) { if (!quiet) - printf("Removing %s\n", - directory.buf + prefix_offset); + printf("Removing %s\n", qname); if (remove_dir_recursively(&directory, 0) != 0) { - warning("failed to remove '%s'", - directory.buf + prefix_offset); + warning("failed to remove '%s'", qname); errors++; } } else if (show_only) { - printf("Would not remove %s\n", - directory.buf + prefix_offset); + printf("Would not remove %s\n", qname); } else { - printf("Not removing %s\n", - directory.buf + prefix_offset); + printf("Not removing %s\n", qname); } strbuf_reset(&directory); } else { if (pathspec && !matches) continue; + qname = quote_path_relative(ent->name, -1, &buf, prefix); if (show_only) { - printf("Would remove %s\n", - ent->name + prefix_offset); + printf("Would remove %s\n", qname); continue; } else if (!quiet) { - printf("Removing %s\n", - ent->name + prefix_offset); + printf("Removing %s\n", qname); } if (unlink(ent->name) != 0) { - warning("failed to remove '%s'", ent->name); + warning("failed to remove '%s'", qname); errors++; } } diff --git a/builtin-commit.c b/builtin-commit.c index f49c22e642..660a3458f7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -198,6 +198,8 @@ static void create_base_index(void) opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; opts.fn = oneway_merge; tree = parse_tree_indirect(head_sha1); diff --git a/builtin-describe.c b/builtin-describe.c index 2f1e7ba150..df554b30af 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -21,6 +21,7 @@ static int longformat; static int abbrev = DEFAULT_ABBREV; static int max_candidates = 10; const char *pattern = NULL; +static int always; struct commit_name { struct tag *tag; @@ -156,7 +157,7 @@ static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(n->sha1); - if (!n->tag || !n->tag->tag) + if (!n->tag || parse_tag(n->tag) || !n->tag->tag) die("annotated tag %s not available", n->path); if (strcmp(n->tag->tag, n->path)) warning("tag '%s' is really '%s' here", n->tag->tag, n->path); @@ -166,9 +167,11 @@ static void display_name(struct commit_name *n) printf("%s", n->tag->tag); else printf("%s", n->path); - if (longformat) - printf("-0-g%s", - find_unique_abbrev(n->tag->tagged->sha1, abbrev)); +} + +static void show_suffix(int depth, const unsigned char *sha1) +{ + printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev)); } static void describe(const char *arg, int last_one) @@ -195,7 +198,12 @@ static void describe(const char *arg, int last_one) n = cmit->util; if (n) { + /* + * Exact match to an existing ref. + */ display_name(n); + if (longformat) + show_suffix(0, n->tag->tagged->sha1); printf("\n"); return; } @@ -250,8 +258,14 @@ static void describe(const char *arg, int last_one) } } - if (!match_cnt) - die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1)); + if (!match_cnt) { + const unsigned char *sha1 = cmit->object.sha1; + if (always) { + printf("%s\n", find_unique_abbrev(sha1, abbrev)); + return; + } + die("cannot describe '%s'", sha1_to_hex(sha1)); + } qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); @@ -281,8 +295,7 @@ static void describe(const char *arg, int last_one) display_name(all_matches[0].name); if (abbrev) - printf("-%d-g%s", all_matches[0].depth, - find_unique_abbrev(cmit->object.sha1, abbrev)); + show_suffix(all_matches[0].depth, cmit->object.sha1); printf("\n"); if (!last_one) @@ -305,6 +318,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix) "consider <n> most recent tags (default: 10)"), OPT_STRING(0, "match", &pattern, "pattern", "only consider tags matching <pattern>"), + OPT_BOOLEAN(0, "always", &always, + "show abbreviated commit object as fallback"), OPT_END(), }; @@ -320,11 +335,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die("--long is incompatible with --abbrev=0"); if (contains) { - const char **args = xmalloc((6 + argc) * sizeof(char*)); + const char **args = xmalloc((7 + argc) * sizeof(char*)); int i = 0; args[i++] = "name-rev"; args[i++] = "--name-only"; args[i++] = "--no-undefined"; + if (always) + args[i++] = "--always"; if (!all) { args[i++] = "--tags"; if (pattern) { diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index b23e886d75..7b28024224 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -18,7 +18,7 @@ static struct fetch_pack_args args = { }; 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>...]"; +"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; #define COMPLETE (1U << 0) #define COMMON (1U << 1) @@ -41,7 +41,8 @@ static void rev_list_push(struct commit *commit, int mark) commit->object.flags |= mark; if (!(commit->object.parsed)) - parse_commit(commit); + if (parse_commit(commit)) + return; insert_by_date(commit, &rev_list); @@ -83,7 +84,8 @@ static void mark_common(struct commit *commit, if (!ancestors_only && !(o->flags & POPPED)) non_common_revs--; if (!o->parsed && !dont_parse) - parse_commit(commit); + if (parse_commit(commit)) + return; for (parents = commit->parents; parents; @@ -103,20 +105,20 @@ static const unsigned char* get_rev(void) while (commit == NULL) { unsigned int mark; - struct commit_list* parents; + struct commit_list *parents = NULL; if (rev_list == NULL || non_common_revs == 0) return NULL; commit = rev_list->item; if (!(commit->object.parsed)) - parse_commit(commit); + if (!parse_commit(commit)) + parents = commit->parents; + commit->object.flags |= POPPED; if (!(commit->object.flags & COMMON)) non_common_revs--; - parents = commit->parents; - if (commit->object.flags & COMMON) { /* do not send "have", and ignore ancestors */ commit = NULL; @@ -174,13 +176,14 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s%s%s\n", + packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n", sha1_to_hex(remote), (multi_ack ? " multi_ack" : ""), (use_sideband == 2 ? " side-band-64k" : ""), (use_sideband == 1 ? " side-band" : ""), (args.use_thin_pack ? " thin-pack" : ""), (args.no_progress ? " no-progress" : ""), + (args.include_tag ? " include-tag" : ""), " ofs-delta"); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); @@ -212,7 +215,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (!lookup_object(sha1)) die("object not found: %s", line); /* make sure that it is parsed as shallow */ - parse_object(sha1); + if (!parse_object(sha1)) + die("error in object: %s", line); if (unregister_shallow(sha1)) die("no shallow found: %s", line); continue; @@ -680,6 +684,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp("--include-tag", arg)) { + args.include_tag = 1; + continue; + } if (!strcmp("--all", arg)) { args.fetch_all = 1; continue; diff --git a/builtin-fetch.c b/builtin-fetch.c index ac335f20fe..b2b9935ed6 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -40,6 +40,8 @@ static struct option builtin_fetch_options[] = { "force overwrite of local branch"), OPT_SET_INT('t', "tags", &tags, "fetch all tags and associated objects", TAGS_SET), + OPT_SET_INT('n', NULL, &tags, + "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -101,6 +103,10 @@ static void add_merge_config(struct ref **head, } } +static void find_non_local_tags(struct transport *transport, + struct ref **head, + struct ref ***tail); + static struct ref *get_ref_map(struct transport *transport, struct refspec *refs, int ref_count, int tags, int *autotags) @@ -157,8 +163,11 @@ static struct ref *get_ref_map(struct transport *transport, if (!ref_map) die("Couldn't find remote ref HEAD"); ref_map->merge = 1; + tail = &ref_map->next; } } + if (tags == TAGS_DEFAULT && *autotags) + find_non_local_tags(transport, &ref_map, &tail); ref_remove_duplicates(ref_map); return ref_map; @@ -452,18 +461,28 @@ static int add_existing(const char *refname, const unsigned char *sha1, return 0; } -static struct ref *find_non_local_tags(struct transport *transport, - struct ref *fetch_map) +static int will_fetch(struct ref **head, const unsigned char *sha1) +{ + struct ref *rm = *head; + while (rm) { + if (!hashcmp(rm->old_sha1, sha1)) + return 1; + rm = rm->next; + } + return 0; +} + +static void find_non_local_tags(struct transport *transport, + struct ref **head, + struct ref ***tail) { - static struct path_list existing_refs = { NULL, 0, 0, 0 }; + 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); @@ -489,7 +508,8 @@ static struct ref *find_non_local_tags(struct transport *transport, if (!path_list_has_path(&existing_refs, ref_name) && !path_list_has_path(&new_refs, ref_name) && - has_sha1_file(ref->old_sha1)) { + (has_sha1_file(ref->old_sha1) || + will_fetch(head, ref->old_sha1))) { path_list_insert(ref_name, &new_refs); rm = alloc_ref(strlen(ref_name) + 1); @@ -498,19 +518,19 @@ static struct ref *find_non_local_tags(struct transport *transport, strcpy(rm->peer_ref->name, ref_name); hashcpy(rm->old_sha1, ref_sha1); - *tail = rm; - tail = &rm->next; + **tail = rm; + *tail = &rm->next; } free(ref_name); } - - return ref_map; + path_list_clear(&existing_refs, 0); + path_list_clear(&new_refs, 0); } static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { - struct ref *ref_map, *fetch_map; + struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) @@ -537,26 +557,28 @@ static int do_fetch(struct transport *transport, read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); } + if (tags == TAGS_DEFAULT && autotags) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (fetch_refs(transport, ref_map)) { free_refs(ref_map); return 1; } - - fetch_map = ref_map; + free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag * following ... */ if (tags == TAGS_DEFAULT && autotags) { - ref_map = find_non_local_tags(transport, fetch_map); + struct ref **tail = &ref_map; + ref_map = NULL; + find_non_local_tags(transport, &ref_map, &tail); if (ref_map) { + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); fetch_refs(transport, ref_map); } free_refs(ref_map); } - free_refs(fetch_map); - transport_disconnect(transport); return 0; diff --git a/builtin-gc.c b/builtin-gc.c index 045bf0e487..95917d74a8 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -26,12 +26,13 @@ static int pack_refs = 1; static int aggressive_window = -1; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 20; +static char *prune_expire = "2.weeks.ago"; #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", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", NULL}; +static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; static const char *argv_rerere[] = {"rerere", "gc", NULL}; static int gc_config(const char *var, const char *value) @@ -55,6 +56,17 @@ static int gc_config(const char *var, const char *value) gc_auto_pack_limit = git_config_int(var, value); return 0; } + if (!strcmp(var, "gc.pruneexpire")) { + if (!value) + return config_error_nonbool(var); + if (strcmp(value, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(value) >= now) + return error("Invalid %s: '%s'", var, value); + } + prune_expire = xstrdup(value); + return 0; + } return git_default_config(var, value); } @@ -234,7 +246,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) return error(FAILED_RUN, argv_repack[0]); - if (prune && run_command_v_opt(argv_prune, RUN_GIT_CMD)) + argv_prune[2] = prune_expire; + if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) return error(FAILED_RUN, argv_prune[0]); if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) diff --git a/builtin-log.c b/builtin-log.c index fe8fc6f22a..5c00725f03 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -20,6 +20,7 @@ static int default_show_root = 1; static const char *fmt_patch_subject_prefix = "PATCH"; +static const char *fmt_pretty; static void add_name_decoration(const char *prefix, const char *name, struct object *obj) { @@ -54,6 +55,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; + if (fmt_pretty) + rev->commit_format = get_commit_format(fmt_pretty); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); rev->show_root_diff = default_show_root; @@ -221,6 +224,8 @@ static int cmd_log_walk(struct rev_info *rev) static int git_log_config(const char *var, const char *value) { + if (!strcmp(var, "format.pretty")) + return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) { if (!value) config_error_nonbool(var); @@ -657,6 +662,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int i; const char *encoding = "utf-8"; struct diff_options opts; + int need_8bit_cte = 0; if (rev->commit_format != CMIT_FMT_EMAIL) die("Cover letter needs email format"); @@ -667,7 +673,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, head_sha1 = sha1_to_hex(head->object.sha1); - log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers); + log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers, + &need_8bit_cte); committer = git_committer_info(0); @@ -676,7 +683,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, encoding); pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, - encoding, 0); + encoding, need_8bit_cte); pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); printf("%s\n", sb.buf); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 25dbfb4499..dc7eab89b3 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -574,17 +574,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv + i); /* Verify that the pathspec matches the prefix */ - if (pathspec) { - if (argc != i) { - int cnt; - for (cnt = 0; pathspec[cnt]; cnt++) - ; - if (cnt != (argc - i)) - exit(1); /* error message already given */ - } + if (pathspec) prefix = verify_pathspec(prefix); - } else if (argc != i) - exit(1); /* error message already given */ /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 023754986e..8907a89d6c 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -94,11 +94,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); ref = transport_get_remote_refs(transport); - transport_disconnect(transport); - - if (!ref) + if (transport_disconnect(transport)) return 1; - for ( ; ref; ref = ref->next) { if (!check_ref_type(ref, flags)) continue; diff --git a/builtin-merge-file.c b/builtin-merge-file.c index adce6d4635..3605960c2d 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -57,7 +57,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (!f) ret = error("Could not open %s for writing", filename); - else if (fwrite(result.ptr, result.size, 1, f) != 1) + else if (result.size && + fwrite(result.ptr, result.size, 1, f) != 1) ret = error("Could not write to %s", filename); else if (fclose(f)) ret = error("Could not close %s", filename); diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 6fe4102c0c..910c0d20e7 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -11,11 +11,11 @@ #include "tree-walk.h" #include "diff.h" #include "diffcore.h" -#include "run-command.h" #include "tag.h" #include "unpack-trees.h" #include "path-list.h" #include "xdiff-interface.h" +#include "ll-merge.h" #include "interpolate.h" #include "attr.h" #include "merge-recursive.h" @@ -213,6 +213,8 @@ static int git_merge_trees(int index_only, opts.merge = 1; opts.head_idx = 2; opts.fn = threeway_merge; + opts.src_index = &the_index; + opts.dst_index = &the_index; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -615,364 +617,16 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm) mm->size = size; } -/* - * Customizable low-level merge drivers support. - */ - -struct ll_merge_driver; -typedef int (*ll_merge_fn)(const struct ll_merge_driver *, - const char *path, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result); - -struct ll_merge_driver { - const char *name; - const char *description; - ll_merge_fn fn; - const char *recursive; - struct ll_merge_driver *next; - char *cmdline; -}; - -/* - * Built-in low-levels - */ -static int ll_binary_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - /* - * The tentative merge result is "ours" for the final round, - * or common ancestor for an internal merge. Still return - * "conflicted merge" status. - */ - mmfile_t *stolen = index_only ? orig : src1; - - result->ptr = stolen->ptr; - result->size = stolen->size; - stolen->ptr = NULL; - return 1; -} - -static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - xpparam_t xpp; - - if (buffer_is_binary(orig->ptr, orig->size) || - buffer_is_binary(src1->ptr, src1->size) || - buffer_is_binary(src2->ptr, src2->size)) { - warning("Cannot merge binary files: %s vs. %s\n", - name1, name2); - return ll_binary_merge(drv_unused, path_unused, - orig, src1, name1, - src2, name2, - result); - } - - memset(&xpp, 0, sizeof(xpp)); - return xdl_merge(orig, - src1, name1, - src2, name2, - &xpp, XDL_MERGE_ZEALOUS, - result); -} - -static int ll_union_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - char *src, *dst; - long size; - const int marker_size = 7; - - int status = ll_xdl_merge(drv_unused, path_unused, - orig, src1, NULL, src2, NULL, result); - if (status <= 0) - return status; - size = result->size; - src = dst = result->ptr; - while (size) { - char ch; - if ((marker_size < size) && - (*src == '<' || *src == '=' || *src == '>')) { - int i; - ch = *src; - for (i = 0; i < marker_size; i++) - if (src[i] != ch) - goto not_a_marker; - if (src[marker_size] != '\n') - goto not_a_marker; - src += marker_size + 1; - size -= marker_size + 1; - continue; - } - not_a_marker: - do { - ch = *src++; - *dst++ = ch; - size--; - } while (ch != '\n' && size); - } - result->size = dst - result->ptr; - return 0; -} - -#define LL_BINARY_MERGE 0 -#define LL_TEXT_MERGE 1 -#define LL_UNION_MERGE 2 -static struct ll_merge_driver ll_merge_drv[] = { - { "binary", "built-in binary merge", ll_binary_merge }, - { "text", "built-in 3-way text merge", ll_xdl_merge }, - { "union", "built-in union merge", ll_union_merge }, -}; - -static void create_temp(mmfile_t *src, char *path) -{ - int fd; - - strcpy(path, ".merge_file_XXXXXX"); - fd = xmkstemp(path); - if (write_in_full(fd, src->ptr, src->size) != src->size) - die("unable to write temp-file"); - close(fd); -} - -/* - * User defined low-level merge driver support. - */ -static int ll_ext_merge(const struct ll_merge_driver *fn, - const char *path, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - char temp[3][50]; - char cmdbuf[2048]; - struct interp table[] = { - { "%O" }, - { "%A" }, - { "%B" }, - }; - struct child_process child; - const char *args[20]; - int status, fd, i; - struct stat st; - - if (fn->cmdline == NULL) - die("custom merge driver %s lacks command line.", fn->name); - - result->ptr = NULL; - result->size = 0; - create_temp(orig, temp[0]); - create_temp(src1, temp[1]); - create_temp(src2, temp[2]); - - interp_set_entry(table, 0, temp[0]); - interp_set_entry(table, 1, temp[1]); - interp_set_entry(table, 2, temp[2]); - - output(1, "merging %s using %s", path, - fn->description ? fn->description : fn->name); - - interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3); - - memset(&child, 0, sizeof(child)); - child.argv = args; - args[0] = "sh"; - args[1] = "-c"; - args[2] = cmdbuf; - args[3] = NULL; - - status = run_command(&child); - if (status < -ERR_RUN_COMMAND_FORK) - ; /* failure in run-command */ - else - status = -status; - fd = open(temp[1], O_RDONLY); - if (fd < 0) - goto bad; - if (fstat(fd, &st)) - goto close_bad; - result->size = st.st_size; - result->ptr = xmalloc(result->size + 1); - if (read_in_full(fd, result->ptr, result->size) != result->size) { - free(result->ptr); - result->ptr = NULL; - result->size = 0; - } - close_bad: - close(fd); - bad: - for (i = 0; i < 3; i++) - unlink(temp[i]); - return status; -} - -/* - * merge.default and merge.driver configuration items - */ -static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; - -static int read_merge_config(const char *var, const char *value) -{ - struct ll_merge_driver *fn; - const char *ep, *name; - int namelen; - - if (!strcmp(var, "merge.default")) { - if (!value) - return config_error_nonbool(var); - default_ll_merge = strdup(value); - return 0; - } - - /* - * We are not interested in anything but "merge.<name>.variable"; - * especially, we do not want to look at variables such as - * "merge.summary", "merge.tool", and "merge.verbosity". - */ - if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5) - return 0; - - /* - * Find existing one as we might be processing merge.<name>.var2 - * after seeing merge.<name>.var1. - */ - name = var + 6; - namelen = ep - name; - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) - break; - if (!fn) { - fn = xcalloc(1, sizeof(struct ll_merge_driver)); - fn->name = xmemdupz(name, namelen); - fn->fn = ll_ext_merge; - *ll_user_merge_tail = fn; - ll_user_merge_tail = &(fn->next); - } - - ep++; - - if (!strcmp("name", ep)) { - if (!value) - return config_error_nonbool(var); - fn->description = strdup(value); - return 0; - } - - if (!strcmp("driver", ep)) { - if (!value) - return config_error_nonbool(var); - /* - * merge.<name>.driver specifies the command line: - * - * command-line - * - * The command-line will be interpolated with the following - * tokens and is given to the shell: - * - * %O - temporary file name for the merge base. - * %A - temporary file name for our version. - * %B - temporary file name for the other branches' version. - * - * The external merge driver should write the results in the - * file named by %A, and signal that it has done with zero exit - * status. - */ - fn->cmdline = strdup(value); - return 0; - } - - if (!strcmp("recursive", ep)) { - if (!value) - return config_error_nonbool(var); - fn->recursive = strdup(value); - return 0; - } - - return 0; -} - -static void initialize_ll_merge(void) -{ - if (ll_user_merge_tail) - return; - ll_user_merge_tail = &ll_user_merge; - git_config(read_merge_config); -} - -static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) -{ - struct ll_merge_driver *fn; - const char *name; - int i; - - initialize_ll_merge(); - - if (ATTR_TRUE(merge_attr)) - return &ll_merge_drv[LL_TEXT_MERGE]; - else if (ATTR_FALSE(merge_attr)) - return &ll_merge_drv[LL_BINARY_MERGE]; - else if (ATTR_UNSET(merge_attr)) { - if (!default_ll_merge) - return &ll_merge_drv[LL_TEXT_MERGE]; - else - name = default_ll_merge; - } - else - name = merge_attr; - - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strcmp(fn->name, name)) - return fn; - - for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) - if (!strcmp(ll_merge_drv[i].name, name)) - return &ll_merge_drv[i]; - - /* default to the 3-way */ - return &ll_merge_drv[LL_TEXT_MERGE]; -} - -static const char *git_path_check_merge(const char *path) -{ - static struct git_attr_check attr_merge_check; - - if (!attr_merge_check.attr) - attr_merge_check.attr = git_attr("merge", 5); - - if (git_checkattr(path, 1, &attr_merge_check)) - return NULL; - return attr_merge_check.value; -} - -static int ll_merge(mmbuffer_t *result_buf, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b, - const char *branch1, - const char *branch2) +static int merge_3way(mmbuffer_t *result_buf, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) { mmfile_t orig, src1, src2; char *name1, *name2; int merge_status; - const char *ll_driver_name; - const struct ll_merge_driver *driver; name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); @@ -981,14 +635,9 @@ static int ll_merge(mmbuffer_t *result_buf, fill_mm(a->sha1, &src1); fill_mm(b->sha1, &src2); - ll_driver_name = git_path_check_merge(a->path); - driver = find_ll_merge_driver(ll_driver_name); - - if (index_only && driver->recursive) - driver = find_ll_merge_driver(driver->recursive); - merge_status = driver->fn(driver, a->path, - &orig, &src1, name1, &src2, name2, - result_buf); + merge_status = ll_merge(result_buf, a->path, &orig, + &src1, name1, &src2, name2, + index_only); free(name1); free(name2); @@ -1019,9 +668,20 @@ static struct merge_file_info merge_file(struct diff_filespec *o, if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) result.merge = 1; - result.mode = a->mode == o->mode ? b->mode: a->mode; + /* + * Merge modes + */ + if (a->mode == b->mode || a->mode == o->mode) + result.mode = b->mode; + else { + result.mode = a->mode; + if (b->mode != o->mode) { + result.clean = 0; + result.merge = 1; + } + } - if (sha_eq(a->sha1, o->sha1)) + if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) hashcpy(result.sha, b->sha1); else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); @@ -1029,8 +689,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o, mmbuffer_t result_buf; int merge_status; - merge_status = ll_merge(&result_buf, o, a, b, - branch1, branch2); + merge_status = merge_3way(&result_buf, o, a, b, + branch1, branch2); if ((merge_status < 0) || !result_buf.ptr) die("Failed to execute internal merge"); diff --git a/builtin-mv.c b/builtin-mv.c index 68aa2a68bb..94f6dd2aad 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -19,7 +19,6 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, int count, int base_name) { int i; - int len = prefix ? strlen(prefix) : 0; const char **result = xmalloc((count + 1) * sizeof(const char *)); memcpy(result, pathspec, count * sizeof(const char *)); result[count] = NULL; @@ -33,11 +32,8 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, if (last_slash) result[i] = last_slash + 1; } - result[i] = prefix_path(prefix, len, result[i]); - if (!result[i]) - exit(1); /* error already given */ } - return result; + return get_pathspec(prefix, result); } static void show_list(const char *label, struct path_list *list) diff --git a/builtin-name-rev.c b/builtin-name-rev.c index f22c8b5f5d..384da4db13 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -125,7 +125,7 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void } /* returns a static buffer */ -static const char *get_rev_name(struct object *o) +static const char *get_rev_name(const struct object *o) { static char buffer[1024]; struct rev_name *n; @@ -151,6 +151,26 @@ static const char *get_rev_name(struct object *o) } } +static void show_name(const struct object *obj, + const char *caller_name, + int always, int allow_undefined, int name_only) +{ + const char *name; + const unsigned char *sha1 = obj->sha1; + + if (!name_only) + printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1)); + name = get_rev_name(obj); + if (name) + printf("%s\n", name); + else if (allow_undefined) + printf("undefined\n"); + else if (always) + printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV)); + else + die("cannot describe '%s'", sha1_to_hex(sha1)); +} + static char const * const name_rev_usage[] = { "git-name-rev [options] ( --all | --stdin | <commit>... )", NULL @@ -159,7 +179,7 @@ static char const * const name_rev_usage[] = { int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; - int all = 0, transform_stdin = 0, allow_undefined = 1; + int all = 0, transform_stdin = 0, allow_undefined = 1, always = 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)"), @@ -170,6 +190,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), + OPT_BOOLEAN(0, "always", &always, + "show abbreviated commit object as fallback"), OPT_END(), }; @@ -258,35 +280,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) int i, max; max = get_max_object_index(); - for (i = 0; i < max; i++) { - struct object * obj = get_indexed_object(i); - const char *name; - if (!obj) - continue; - if (!data.name_only) - printf("%s ", sha1_to_hex(obj->sha1)); - name = get_rev_name(obj); - if (name) - printf("%s\n", name); - else if (allow_undefined) - printf("undefined\n"); - else - die("cannot describe '%s'", sha1_to_hex(obj->sha1)); - } + for (i = 0; i < max; i++) + show_name(get_indexed_object(i), NULL, + always, allow_undefined, data.name_only); } else { int i; - for (i = 0; i < revs.nr; i++) { - const char *name; - if (!data.name_only) - printf("%s ", revs.objects[i].name); - name = get_rev_name(revs.objects[i].item); - if (name) - printf("%s\n", name); - else if (allow_undefined) - printf("undefined\n"); - else - die("cannot describe '%s'", sha1_to_hex(revs.objects[i].item->sha1)); - } + for (i = 0; i < revs.nr; i++) + show_name(revs.objects[i].item, revs.objects[i].name, + always, allow_undefined, data.name_only); } return 0; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 2799e68338..777f272668 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,6 +15,7 @@ #include "revision.h" #include "list-objects.h" #include "progress.h" +#include "refs.h" #ifdef THREADED_DELTA_SEARCH #include "thread-utils.h" @@ -27,7 +28,8 @@ git-pack-objects [{ -q | --progress | --all-progress }] \n\ [--window=N] [--window-memory=N] [--depth=N] \n\ [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\ [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ - [--stdout | base-name] [--keep-unreachable] [<ref-list | <object-list]"; + [--stdout | base-name] [--include-tag] [--keep-unreachable] \n\ + [<ref-list | <object-list]"; struct object_entry { struct pack_idx_entry idx; @@ -63,7 +65,7 @@ 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, keep_unreachable; +static int no_reuse_delta, no_reuse_object, keep_unreachable, include_tag; static int local; static int incremental; static int allow_ofs_delta; @@ -452,6 +454,7 @@ static void write_pack_file(void) struct pack_header hdr; int do_progress = progress >> pack_to_stdout; uint32_t nr_remaining = nr_result; + time_t last_mtime = 0; if (do_progress) progress_state = start_progress("Writing objects", nr_result); @@ -502,6 +505,7 @@ static void write_pack_file(void) if (!pack_to_stdout) { mode_t mode = umask(0); + struct stat st; char *idx_tmp_name, tmpname[PATH_MAX]; umask(mode); @@ -509,6 +513,7 @@ static void write_pack_file(void) 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)) @@ -517,6 +522,28 @@ static void write_pack_file(void) if (rename(pack_tmp_name, tmpname)) die("unable to rename temporary pack file: %s", strerror(errno)); + + /* + * Packs are runtime accessed in their mtime + * order since newer packs are more likely to contain + * younger objects. So if we are creating multiple + * packs then we should modify the mtime of later ones + * to preserve this property. + */ + if (stat(tmpname, &st) < 0) { + warning("failed to stat %s: %s", + tmpname, strerror(errno)); + } else if (!last_mtime) { + last_mtime = st.st_mtime; + } else { + struct utimbuf utb; + utb.actime = st.st_atime; + utb.modtime = --last_mtime; + if (utime(tmpname, &utb) < 0) + warning("failed utime() on %s: %s", + tmpname, strerror(errno)); + } + snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", base_name, sha1_to_hex(sha1)); if (adjust_perm(idx_tmp_name, mode)) @@ -525,6 +552,7 @@ 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)); @@ -1630,6 +1658,18 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, #define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) #endif +static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + unsigned char peeled[20]; + + if (!prefixcmp(path, "refs/tags/") && /* is a tag? */ + !peel_ref(path, peeled) && /* peelable? */ + !is_null_sha1(peeled) && /* annotated tag? */ + locate_object_entry(peeled)) /* object packed? */ + add_object_entry(sha1, OBJ_TAG, NULL, 0); + return 0; +} + static void prepare_pack(int window, int depth) { struct object_entry **delta_list; @@ -2033,6 +2073,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) keep_unreachable = 1; continue; } + if (!strcmp("--include-tag", arg)) { + include_tag = 1; + continue; + } if (!strcmp("--unpacked", arg) || !prefixcmp(arg, "--unpacked=") || !strcmp("--reflog", arg) || @@ -2109,6 +2153,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } + if (include_tag && nr_result) + for_each_ref(add_ref_tag, NULL); stop_progress(&progress_state); if (non_empty && !nr_result) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 0138f5a917..e9cfd2bbc5 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,16 +13,15 @@ #include "dir.h" #include "builtin.h" -#define MAX_TREES 8 static int nr_trees; -static struct tree *trees[MAX_TREES]; +static struct tree *trees[MAX_UNPACK_TREES]; static int list_tree(unsigned char *sha1) { struct tree *tree; - if (nr_trees >= MAX_TREES) - die("I cannot read more than %d trees", MAX_TREES); + if (nr_trees >= MAX_UNPACK_TREES) + die("I cannot read more than %d trees", MAX_UNPACK_TREES); tree = parse_tree_indirect(sha1); if (!tree) return -1; @@ -97,11 +96,13 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; - struct tree_desc t[MAX_TREES]; + struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; + opts.src_index = &the_index; + opts.dst_index = &the_index; git_config(git_default_config); @@ -220,27 +221,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if ((opts.dir && !opts.update)) die("--exclude-per-directory is meaningless unless -u"); - if (opts.prefix) { - int pfxlen = strlen(opts.prefix); - int pos; - if (opts.prefix[pfxlen-1] != '/') - die("prefix must end with /"); - if (stage != 2) - die("binding merge takes only one tree"); - pos = cache_name_pos(opts.prefix, pfxlen); - if (0 <= pos) - die("corrupt index file"); - pos = -pos-1; - if (pos < active_nr && - !strncmp(active_cache[pos]->name, opts.prefix, pfxlen)) - die("subdirectory '%s' already exists.", opts.prefix); - pos = cache_name_pos(opts.prefix, pfxlen-1); - if (0 <= pos) - die("file '%.*s' already exists.", - pfxlen-1, opts.prefix); - opts.pos = -1 - pos; - } - if (opts.merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); diff --git a/builtin-reflog.c b/builtin-reflog.c index ab53c8cb7c..280e24e151 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -14,6 +14,8 @@ static const char reflog_expire_usage[] = "git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +static const char reflog_delete_usage[] = +"git-reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -22,9 +24,12 @@ struct cmd_reflog_expire_cb { struct rev_info revs; int dry_run; int stalefix; + int rewrite; + int updateref; int verbose; unsigned long expire_total; unsigned long expire_unreachable; + int recno; }; struct expire_reflog_cb { @@ -32,6 +37,7 @@ struct expire_reflog_cb { const char *ref; struct commit *ref_commit; struct cmd_reflog_expire_cb *cmd; + unsigned char last_kept_sha1[20]; }; struct collected_reflog { @@ -213,6 +219,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (timestamp < cb->cmd->expire_total) goto prune; + if (cb->cmd->rewrite) + osha1 = cb->last_kept_sha1; + old = new = NULL; if (cb->cmd->stalefix && (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) @@ -230,6 +239,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, goto prune; } + if (cb->cmd->recno && --(cb->cmd->recno) == 0) + goto prune; + if (cb->newlog) { char sign = (tz < 0) ? '-' : '+'; int zone = (tz < 0) ? (-tz) : tz; @@ -237,6 +249,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, sha1_to_hex(osha1), sha1_to_hex(nsha1), email, timestamp, sign, zone, message); + hashcpy(cb->last_kept_sha1, nsha1); } if (cb->cmd->verbose) printf("keep %s", message); @@ -280,10 +293,20 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, status |= error("%s: %s", strerror(errno), newlog_path); unlink(newlog_path); + } else if (cmd->updateref && + (write_in_full(lock->lock_fd, + sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + write_in_full(lock->lock_fd, "\n", 1) != 1 || + close_ref(lock) < 0)) { + status |= error("Couldn't write %s", + lock->lk->filename); + unlink(newlog_path); } else if (rename(newlog_path, log_file)) { status |= error("cannot rename %s to %s", newlog_path, log_file); unlink(newlog_path); + } else if (cmd->updateref && commit_ref(lock)) { + status |= error("Couldn't set %s", lock->ref_name); } } free(newlog_path); @@ -358,6 +381,10 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) cb.expire_unreachable = approxidate(arg + 21); else if (!strcmp(arg, "--stale-fix")) cb.stalefix = 1; + else if (!strcmp(arg, "--rewrite")) + cb.rewrite = 1; + else if (!strcmp(arg, "--updateref")) + cb.updateref = 1; else if (!strcmp(arg, "--all")) do_all = 1; else if (!strcmp(arg, "--verbose")) @@ -406,6 +433,78 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) return status; } +static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct cmd_reflog_expire_cb *cb = cb_data; + if (!cb->expire_total || timestamp < cb->expire_total) + cb->recno++; + return 0; +} + +static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) +{ + struct cmd_reflog_expire_cb cb; + int i, status = 0; + + memset(&cb, 0, sizeof(cb)); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) + cb.dry_run = 1; + else if (!strcmp(arg, "--rewrite")) + cb.rewrite = 1; + else if (!strcmp(arg, "--updateref")) + cb.updateref = 1; + else if (!strcmp(arg, "--verbose")) + cb.verbose = 1; + else if (!strcmp(arg, "--")) { + i++; + break; + } + else if (arg[0] == '-') + usage(reflog_delete_usage); + else + break; + } + + if (argc - i < 1) + return error("Nothing to delete?"); + + for ( ; i < argc; i++) { + const char *spec = strstr(argv[i], "@{"); + unsigned char sha1[20]; + char *ep, *ref; + int recno; + + if (!spec) { + status |= error("Not a reflog: %s", argv[i]); + continue; + } + + if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) { + status |= error("%s points nowhere!", argv[i]); + continue; + } + + recno = strtoul(spec + 2, &ep, 10); + if (*ep == '}') { + cb.recno = -recno; + for_each_reflog_ent(ref, count_reflog_ent, &cb); + } else { + cb.expire_total = approxidate(spec + 2); + for_each_reflog_ent(ref, count_reflog_ent, &cb); + cb.expire_total = 0; + } + + status |= expire_reflog(ref, sha1, 0, &cb); + free(ref); + } + return status; +} + /* * main "reflog" */ @@ -425,6 +524,9 @@ int cmd_reflog(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "expire")) return cmd_reflog_expire(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "delete")) + return cmd_reflog_delete(argc - 1, argv + 1, prefix); + /* Not a recognized reflog command..*/ usage(reflog_usage); } diff --git a/builtin-remote.c b/builtin-remote.c new file mode 100644 index 0000000000..24e692953b --- /dev/null +++ b/builtin-remote.c @@ -0,0 +1,605 @@ +#include "cache.h" +#include "parse-options.h" +#include "transport.h" +#include "remote.h" +#include "path-list.h" +#include "strbuf.h" +#include "run-command.h" +#include "refs.h" + +static const char * const builtin_remote_usage[] = { + "git remote", + "git remote add <name> <url>", + "git remote rm <name>", + "git remote show <name>", + "git remote prune <name>", + "git remote update [group]", + NULL +}; + +static int verbose; + +static inline int postfixcmp(const char *string, const char *postfix) +{ + int len1 = strlen(string), len2 = strlen(postfix); + if (len1 < len2) + return 1; + return strcmp(string + len1 - len2, postfix); +} + +static inline const char *skip_prefix(const char *name, const char *prefix) +{ + return !name ? "" : + prefixcmp(name, prefix) ? name : name + strlen(prefix); +} + +static int opt_parse_track(const struct option *opt, const char *arg, int not) +{ + struct path_list *list = opt->value; + if (not) + path_list_clear(list, 0); + else + path_list_append(arg, list); + return 0; +} + +static int fetch_remote(const char *name) +{ + const char *argv[] = { "fetch", name, NULL }; + printf("Updating %s\n", name); + if (run_command_v_opt(argv, RUN_GIT_CMD)) + return error("Could not fetch %s", name); + return 0; +} + +static int add(int argc, const char **argv) +{ + int fetch = 0, mirror = 0; + struct path_list track = { NULL, 0, 0 }; + const char *master = NULL; + struct remote *remote; + struct strbuf buf, buf2; + const char *name, *url; + int i; + + struct option options[] = { + OPT_GROUP("add specific options"), + OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"), + OPT_CALLBACK('t', "track", &track, "branch", + "branch(es) to track", opt_parse_track), + OPT_STRING('m', "master", &master, "branch", "master branch"), + OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"), + OPT_END() + }; + + argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + + if (argc < 2) + usage_with_options(builtin_remote_usage, options); + + name = argv[0]; + url = argv[1]; + + remote = remote_get(name); + if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) || + remote->fetch_refspec_nr)) + die("remote %s already exists.", name); + + strbuf_init(&buf, 0); + strbuf_init(&buf2, 0); + + strbuf_addf(&buf, "remote.%s.url", name); + if (git_config_set(buf.buf, url)) + return 1; + + if (track.nr == 0) + path_list_append("*", &track); + for (i = 0; i < track.nr; i++) { + struct path_list_item *item = track.items + i; + + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + + strbuf_reset(&buf2); + if (mirror) + strbuf_addf(&buf2, "refs/%s:refs/%s", + item->path, item->path); + else + strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s", + item->path, name, item->path); + if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) + return 1; + } + + if (fetch && fetch_remote(name)) + return 1; + + if (master) { + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/HEAD", name); + + strbuf_reset(&buf2); + strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master); + + if (create_symref(buf.buf, buf2.buf, "remote add")) + return error("Could not setup master '%s'", master); + } + + strbuf_release(&buf); + strbuf_release(&buf2); + path_list_clear(&track, 0); + + return 0; +} + +struct branch_info { + char *remote; + struct path_list merge; +}; + +static struct path_list branch_list; + +static int config_read_branches(const char *key, const char *value) +{ + if (!prefixcmp(key, "branch.")) { + char *name; + struct path_list_item *item; + struct branch_info *info; + enum { REMOTE, MERGE } type; + + key += 7; + if (!postfixcmp(key, ".remote")) { + name = xstrndup(key, strlen(key) - 7); + type = REMOTE; + } else if (!postfixcmp(key, ".merge")) { + name = xstrndup(key, strlen(key) - 6); + type = MERGE; + } else + return 0; + + item = path_list_insert(name, &branch_list); + + if (!item->util) + item->util = xcalloc(sizeof(struct branch_info), 1); + info = item->util; + if (type == REMOTE) { + if (info->remote) + warning("more than one branch.%s", key); + info->remote = xstrdup(value); + } else { + char *space = strchr(value, ' '); + value = skip_prefix(value, "refs/heads/"); + while (space) { + char *merge; + merge = xstrndup(value, space - value); + path_list_append(merge, &info->merge); + value = skip_prefix(space + 1, "refs/heads/"); + space = strchr(value, ' '); + } + path_list_append(xstrdup(value), &info->merge); + } + } + return 0; +} + +static void read_branches(void) +{ + if (branch_list.nr) + return; + git_config(config_read_branches); + sort_path_list(&branch_list); +} + +struct ref_states { + struct remote *remote; + struct strbuf remote_prefix; + struct path_list new, stale, tracked; +}; + +static int handle_one_branch(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct ref_states *states = cb_data; + struct refspec refspec; + + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(states->remote, &refspec)) { + struct path_list_item *item; + const char *name = skip_prefix(refspec.src, "refs/heads/"); + if (unsorted_path_list_has_path(&states->tracked, name) || + unsorted_path_list_has_path(&states->new, + name)) + return 0; + item = path_list_append(name, &states->stale); + item->util = xstrdup(refname); + } + return 0; +} + +static int get_ref_states(const struct ref *ref, struct ref_states *states) +{ + struct ref *fetch_map = NULL, **tail = &fetch_map; + int i; + + for (i = 0; i < states->remote->fetch_refspec_nr; i++) + if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1)) + die("Could not get fetch map for refspec %s", + states->remote->fetch_refspec[i]); + + states->new.strdup_paths = states->tracked.strdup_paths = 1; + for (ref = fetch_map; ref; ref = ref->next) { + struct path_list *target = &states->tracked; + unsigned char sha1[20]; + void *util = NULL; + + if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) + target = &states->new; + else { + target = &states->tracked; + if (hashcmp(sha1, ref->new_sha1)) + util = &states; + } + path_list_append(skip_prefix(ref->name, "refs/heads/"), + target)->util = util; + } + free_refs(fetch_map); + + strbuf_addf(&states->remote_prefix, + "refs/remotes/%s/", states->remote->name); + for_each_ref(handle_one_branch, states); + sort_path_list(&states->stale); + + return 0; +} + +struct branches_for_remote { + const char *prefix; + struct path_list *branches; +}; + +static int add_branch_for_removal(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct branches_for_remote *branches = cb_data; + + if (!prefixcmp(refname, branches->prefix)) { + struct path_list_item *item; + + /* make sure that symrefs are deleted */ + if (flags & REF_ISSYMREF) + return unlink(git_path(refname)); + + item = path_list_append(refname, branches->branches); + item->util = xmalloc(20); + hashcpy(item->util, sha1); + } + + return 0; +} + +static int remove_branches(struct path_list *branches) +{ + int i, result = 0; + for (i = 0; i < branches->nr; i++) { + struct path_list_item *item = branches->items + i; + const char *refname = item->path; + unsigned char *sha1 = item->util; + + if (delete_ref(refname, sha1)) + result |= error("Could not remove branch %s", refname); + } + return result; +} + +static int rm(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + struct remote *remote; + struct strbuf buf; + struct path_list branches = { NULL, 0, 0, 1 }; + struct branches_for_remote cb_data = { NULL, &branches }; + int i; + + if (argc != 2) + usage_with_options(builtin_remote_usage, options); + + remote = remote_get(argv[1]); + if (!remote) + die("No such remote: %s", argv[1]); + + strbuf_init(&buf, 0); + strbuf_addf(&buf, "remote.%s", remote->name); + if (git_config_rename_section(buf.buf, NULL) < 1) + return error("Could not remove config section '%s'", buf.buf); + + read_branches(); + for (i = 0; i < branch_list.nr; i++) { + struct path_list_item *item = branch_list.items + i; + struct branch_info *info = item->util; + if (info->remote && !strcmp(info->remote, remote->name)) { + const char *keys[] = { "remote", "merge", NULL }, **k; + for (k = keys; *k; k++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.%s", + item->path, *k); + if (git_config_set(buf.buf, NULL)) { + strbuf_release(&buf); + return -1; + } + } + } + } + + /* + * We cannot just pass a function to for_each_ref() which deletes + * the branches one by one, since for_each_ref() relies on cached + * refs, which are invalidated when deleting a branch. + */ + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/", remote->name); + cb_data.prefix = buf.buf; + i = for_each_ref(add_branch_for_removal, &cb_data); + strbuf_release(&buf); + + if (!i) + i = remove_branches(&branches); + path_list_clear(&branches, 1); + + return i; +} + +static void show_list(const char *title, struct path_list *list) +{ + int i; + + if (!list->nr) + return; + + printf(title, list->nr > 1 ? "es" : ""); + printf("\n "); + for (i = 0; i < list->nr; i++) + printf("%s%s", i ? " " : "", list->items[i].path); + printf("\n"); +} + +static int show_or_prune(int argc, const char **argv, int prune) +{ + int dry_run = 0, result = 0; + struct option options[] = { + OPT_GROUP("show specific options"), + OPT__DRY_RUN(&dry_run), + OPT_END() + }; + struct ref_states states; + + argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + + if (argc < 1) + usage_with_options(builtin_remote_usage, options); + + memset(&states, 0, sizeof(states)); + for (; argc; argc--, argv++) { + struct transport *transport; + const struct ref *ref; + struct strbuf buf; + int i, got_states; + + states.remote = remote_get(*argv); + if (!states.remote) + return error("No such remote: %s", *argv); + transport = transport_get(NULL, states.remote->url_nr > 0 ? + states.remote->url[0] : NULL); + ref = transport_get_remote_refs(transport); + transport_disconnect(transport); + + read_branches(); + got_states = get_ref_states(ref, &states); + if (got_states) + result = error("Error getting local info for '%s'", + states.remote->name); + + if (prune) { + struct strbuf buf; + int prefix_len; + + strbuf_init(&buf, 0); + if (states.remote->fetch_refspec_nr == 1 && + states.remote->fetch->pattern && + !strcmp(states.remote->fetch->src, + states.remote->fetch->dst)) + /* handle --mirror remote */ + strbuf_addstr(&buf, "refs/heads/"); + else + strbuf_addf(&buf, "refs/remotes/%s/", *argv); + prefix_len = buf.len; + + for (i = 0; i < states.stale.nr; i++) { + strbuf_setlen(&buf, prefix_len); + strbuf_addstr(&buf, states.stale.items[i].path); + result |= delete_ref(buf.buf, NULL); + } + + strbuf_release(&buf); + goto cleanup_states; + } + + printf("* remote %s\n URL: %s\n", *argv, + states.remote->url_nr > 0 ? + states.remote->url[0] : "(no URL)"); + + for (i = 0; i < branch_list.nr; i++) { + struct path_list_item *branch = branch_list.items + i; + struct branch_info *info = branch->util; + int j; + + if (!info->merge.nr || strcmp(*argv, info->remote)) + continue; + printf(" Remote branch%s merged with 'git pull' " + "while on branch %s\n ", + info->merge.nr > 1 ? "es" : "", + branch->path); + for (j = 0; j < info->merge.nr; j++) + printf(" %s", info->merge.items[j].path); + printf("\n"); + } + + if (got_states) + continue; + strbuf_init(&buf, 0); + strbuf_addf(&buf, " New remote branch%%s (next fetch will " + "store in remotes/%s)", states.remote->name); + show_list(buf.buf, &states.new); + strbuf_release(&buf); + show_list(" Stale tracking branch%s (use 'git remote prune')", + &states.stale); + show_list(" Tracked remote branch%s", + &states.tracked); + + if (states.remote->push_refspec_nr) { + printf(" Local branch%s pushed with 'git push'\n ", + states.remote->push_refspec_nr > 1 ? + "es" : ""); + for (i = 0; i < states.remote->push_refspec_nr; i++) { + struct refspec *spec = states.remote->push + i; + printf(" %s%s%s%s", spec->force ? "+" : "", + skip_prefix(spec->src, "refs/heads/"), + spec->dst ? ":" : "", + skip_prefix(spec->dst, "refs/heads/")); + } + } +cleanup_states: + /* NEEDSWORK: free remote */ + path_list_clear(&states.new, 0); + path_list_clear(&states.stale, 0); + path_list_clear(&states.tracked, 0); + } + + return result; +} + +static int get_one_remote_for_update(struct remote *remote, void *priv) +{ + struct path_list *list = priv; + if (!remote->skip_default_update) + path_list_append(xstrdup(remote->name), list); + return 0; +} + +struct remote_group { + const char *name; + struct path_list *list; +} remote_group; + +static int get_remote_group(const char *key, const char *value) +{ + if (!prefixcmp(key, "remotes.") && + !strcmp(key + 8, remote_group.name)) { + /* split list by white space */ + int space = strcspn(value, " \t\n"); + while (*value) { + if (space > 1) + path_list_append(xstrndup(value, space), + remote_group.list); + value += space + (value[space] != '\0'); + space = strcspn(value, " \t\n"); + } + } + + return 0; +} + +static int update(int argc, const char **argv) +{ + int i, result = 0; + struct path_list list = { NULL, 0, 0, 0 }; + static const char *default_argv[] = { NULL, "default", NULL }; + + if (argc < 2) { + argc = 2; + argv = default_argv; + } + + remote_group.list = &list; + for (i = 1; i < argc; i++) { + remote_group.name = argv[i]; + result = git_config(get_remote_group); + } + + if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) + result = for_each_remote(get_one_remote_for_update, &list); + + for (i = 0; i < list.nr; i++) + result |= fetch_remote(list.items[i].path); + + /* all names were strdup()ed or strndup()ed */ + list.strdup_paths = 1; + path_list_clear(&list, 0); + + return result; +} + +static int get_one_entry(struct remote *remote, void *priv) +{ + struct path_list *list = priv; + + path_list_append(remote->name, list)->util = remote->url_nr ? + (void *)remote->url[0] : NULL; + if (remote->url_nr > 1) + warning("Remote %s has more than one URL", remote->name); + + return 0; +} + +static int show_all(void) +{ + struct path_list list = { NULL, 0, 0 }; + int result = for_each_remote(get_one_entry, &list); + + if (!result) { + int i; + + sort_path_list(&list); + for (i = 0; i < list.nr; i++) { + struct path_list_item *item = list.items + i; + printf("%s%s%s\n", item->path, + verbose ? "\t" : "", + verbose && item->util ? + (const char *)item->util : ""); + } + } + return result; +} + +int cmd_remote(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT__VERBOSE(&verbose), + OPT_END() + }; + int result; + + argc = parse_options(argc, argv, options, builtin_remote_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc < 1) + result = show_all(); + else if (!strcmp(argv[0], "add")) + result = add(argc, argv); + else if (!strcmp(argv[0], "rm")) + result = rm(argc, argv); + else if (!strcmp(argv[0], "show")) + result = show_or_prune(argc, argv, 0); + else if (!strcmp(argv[0], "prune")) + result = show_or_prune(argc, argv, 1); + else if (!strcmp(argv[0], "update")) + result = update(argc, argv); + else { + error("Unknown subcommand: %s", argv[0]); + usage_with_options(builtin_remote_usage, options); + } + + return result ? 1 : 0; +} diff --git a/builtin-reset.c b/builtin-reset.c index bb3e19240a..79424bb26e 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -17,9 +17,13 @@ #include "diffcore.h" #include "tree.h" #include "branch.h" +#include "parse-options.h" -static const char builtin_reset_usage[] = -"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]"; +static const char * const git_reset_usage[] = { + "git-reset [--mixed | --soft | --hard] [-q] [<commit>]", + "git-reset [--mixed] <commit> [--] <paths>...", + NULL +}; static char *args_to_str(const char **argv) { @@ -165,40 +169,31 @@ 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; + int i = 0, 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]; + const struct option options[] = { + OPT_SET_INT(0, "mixed", &reset_type, + "reset HEAD and index", MIXED), + OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), + OPT_SET_INT(0, "hard", &reset_type, + "reset HEAD, index and working tree", HARD), + OPT_BOOLEAN('q', NULL, &quiet, + "disable showing new HEAD in hard reset"), + OPT_END() + }; git_config(git_default_config); + argc = parse_options(argc, argv, options, git_reset_usage, + PARSE_OPT_KEEP_DASHDASH); 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] != '-') + if (i < argc && strcmp(argv[i], "--")) rev = argv[i++]; if (get_sha1(rev, sha1)) @@ -211,8 +206,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) 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 diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 90dbb9d7c1..0351d54435 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -322,18 +322,24 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) o->type = OPTION_CALLBACK; o->help = xstrdup(skipspaces(s)); o->value = &parsed; + o->flags = PARSE_OPT_NOARG; 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; + while (s > sb.buf && strchr("*=?!", s[-1])) { + switch (*--s) { + case '=': + o->flags &= ~PARSE_OPT_NOARG; + break; + case '?': + o->flags &= ~PARSE_OPT_NOARG; + o->flags |= PARSE_OPT_OPTARG; + break; + case '!': + o->flags |= PARSE_OPT_NONEG; + break; + case '*': + o->flags |= PARSE_OPT_HIDDEN; + break; + } } if (s - sb.buf == 1) /* short option only */ diff --git a/builtin-shortlog.c b/builtin-shortlog.c index af31abaaf8..bd795b1db7 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -70,11 +70,12 @@ static void insert_one_record(struct shortlog *log, else free(buffer); + /* Skip any leading whitespace, including any blank lines. */ + while (*oneline && isspace(*oneline)) + oneline++; eol = strchr(oneline, '\n'); if (!eol) eol = oneline + strlen(oneline); - while (*oneline && isspace(*oneline) && *oneline != '\n') - oneline++; if (!prefixcmp(oneline, "[PATCH")) { char *eob = strchr(oneline, ']'); if (eob && (!eol || eob < eol)) @@ -228,7 +229,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) { struct shortlog log; struct rev_info rev; + int nongit; + prefix = setup_git_directory_gently(&nongit); shortlog_init(&log); /* since -n is a shadowed rev argument, parse our args first */ @@ -258,7 +261,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) die ("unrecognized argument: %s", argv[1]); /* assume HEAD if from a tty */ - if (!rev.pending.nr && isatty(0)) + if (!nongit && !rev.pending.nr && isatty(0)) add_head_to_pending(&rev); if (rev.pending.nr == 0) { read_from_stdin(&log); diff --git a/builtin-tag.c b/builtin-tag.c index 28c36fdcd1..8dd959fe1c 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -50,12 +50,15 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e size_t len = strlen(editor); int i = 0; const char *args[6]; + struct strbuf arg0; + strbuf_init(&arg0, 0); if (strcspn(editor, "$ \t'") != len) { /* there are specials */ + strbuf_addf(&arg0, "%s \"$@\"", editor); args[i++] = "sh"; args[i++] = "-c"; - args[i++] = "$0 \"$@\""; + args[i++] = arg0.buf; } args[i++] = editor; args[i++] = path; @@ -63,6 +66,7 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e if (run_command_v_opt_cd_env(args, 0, NULL, env)) die("There was a problem with the editor %s.", editor); + strbuf_release(&arg0); } if (!buffer) diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 9d2a854950..50e07faa12 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -7,13 +7,11 @@ #include "commit.h" #include "tag.h" #include "tree.h" -#include "tree-walk.h" #include "progress.h" #include "decorate.h" -#include "fsck.h" -static int dry_run, quiet, recover, has_errors, strict; -static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] [--strict] < pack-file"; +static int dry_run, quiet, recover, has_errors; +static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; @@ -33,16 +31,6 @@ static struct obj_buffer *lookup_object_buffer(struct object *base) return lookup_decoration(&obj_decorate, base); } -static void add_object_buffer(struct object *object, char *buffer, unsigned long size) -{ - struct obj_buffer *obj; - obj = xcalloc(1, sizeof(struct obj_buffer)); - obj->buffer = buffer; - obj->size = size; - if (add_decoration(&obj_decorate, object, obj)) - die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1)); -} - /* * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. @@ -146,58 +134,9 @@ static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1, struct obj_info { off_t offset; unsigned char sha1[20]; - struct object *obj; }; -#define FLAG_OPEN (1u<<20) -#define FLAG_WRITTEN (1u<<21) - static struct obj_info *obj_list; -unsigned nr_objects; - -static void write_cached_object(struct object *obj) -{ - unsigned char sha1[20]; - struct obj_buffer *obj_buf = lookup_object_buffer(obj); - if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0) - die("failed to write object %s", sha1_to_hex(obj->sha1)); - obj->flags |= FLAG_WRITTEN; -} - -static int check_object(struct object *obj, int type, void *data) -{ - if (!obj) - return 0; - - if (obj->flags & FLAG_WRITTEN) - return 1; - - if (type != OBJ_ANY && obj->type != type) - die("object type mismatch"); - - if (!(obj->flags & FLAG_OPEN)) { - unsigned long size; - int type = sha1_object_info(obj->sha1, &size); - if (type != obj->type || type <= 0) - die("object of unexpected type"); - obj->flags |= FLAG_WRITTEN; - return 1; - } - - if (fsck_object(obj, 1, fsck_error_function)) - die("Error in object"); - if (!fsck_walk(obj, check_object, 0)) - die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); - write_cached_object(obj); - return 1; -} - -static void write_rest(void) -{ - unsigned i; - for (i = 0; i < nr_objects; i++) - check_object(obj_list[i].obj, OBJ_ANY, 0); -} static void added_object(unsigned nr, enum object_type type, void *data, unsigned long size); @@ -205,36 +144,9 @@ static void added_object(unsigned nr, enum object_type type, static void write_object(unsigned nr, enum object_type type, void *buf, unsigned long size) { + if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0) + die("failed to write object"); added_object(nr, type, buf, size); - if (!strict) { - if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0) - die("failed to write object"); - free(buf); - obj_list[nr].obj = 0; - } else if (type == OBJ_BLOB) { - struct blob *blob; - if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0) - die("failed to write object"); - free(buf); - - blob = lookup_blob(obj_list[nr].sha1); - if (blob) - blob->object.flags |= FLAG_WRITTEN; - else - die("invalid blob object"); - obj_list[nr].obj = 0; - } else { - struct object *obj; - int eaten; - hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1); - obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten); - if (!obj) - die("invalid %s", typename(type)); - /* buf is stored via add_object_buffer and in obj, if its a tree or commit */ - add_object_buffer(obj, buf, size); - obj->flags |= FLAG_OPEN; - obj_list[nr].obj = obj; - } } static void resolve_delta(unsigned nr, enum object_type type, @@ -251,6 +163,7 @@ static void resolve_delta(unsigned nr, enum object_type type, die("failed to apply delta"); free(delta); write_object(nr, type, result, result_size); + free(result); } static void added_object(unsigned nr, enum object_type type, @@ -280,8 +193,7 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, if (!dry_run && buf) write_object(nr, type, buf, size); - else - free(buf); + free(buf); } static void unpack_delta_entry(enum object_type type, unsigned long delta_size, @@ -424,8 +336,7 @@ static void unpack_all(void) int i; struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); - - nr_objects = ntohl(hdr->hdr_entries); + unsigned nr_objects = ntohl(hdr->hdr_entries); if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE) die("bad pack file"); @@ -436,7 +347,6 @@ static void unpack_all(void) if (!quiet) progress = start_progress("Unpacking objects", nr_objects); obj_list = xmalloc(nr_objects * sizeof(*obj_list)); - memset(obj_list, 0, nr_objects * sizeof(*obj_list)); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); @@ -472,10 +382,6 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) recover = 1; continue; } - if (!strcmp(arg, "--strict")) { - strict = 1; - continue; - } if (!prefixcmp(arg, "--pack_header=")) { struct pack_header *hdr; char *c; @@ -501,8 +407,6 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) unpack_all(); SHA1_Update(&ctx, buffer, offset); SHA1_Final(sha1, &ctx); - if (strict) - write_rest(); if (hashcmp(fill(20), sha1)) die("final sha1 did not match"); use(20); @@ -67,6 +67,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); 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_remote(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); @@ -346,12 +346,12 @@ extern void verify_non_filename(const char *prefix, const char *name); /* Initialize and use the cache information */ extern int read_index(struct index_state *); extern int read_index_from(struct index_state *, const char *path); -extern int write_index(struct index_state *, int newfd); +extern int write_index(const struct index_state *, int newfd); extern int discard_index(struct index_state *); -extern int unmerged_index(struct index_state *); +extern int unmerged_index(const struct index_state *); extern int verify_path(const char *path); extern int index_name_exists(struct index_state *istate, const char *name, int namelen); -extern int index_name_pos(struct index_state *, const char *name, int namelen); +extern int index_name_pos(const struct index_state *, const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ @@ -368,8 +368,8 @@ extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); #define CE_MATCH_IGNORE_VALID 01 /* do not check the contents but report dirty on racily-clean entries */ #define CE_MATCH_RACY_IS_DIRTY 02 -extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int); -extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, unsigned int); +extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); +extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); 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); @@ -536,6 +536,7 @@ extern int create_symref(const char *ref, const char *refs_heads_master, const c extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); +extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); extern void *read_object_with_reference(const unsigned char *sha1, @@ -543,6 +544,9 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); +extern struct object *peel_to_type(const char *name, int namelen, + struct object *o, enum object_type); + enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, @@ -70,7 +70,7 @@ 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); + int need_8bit_cte); void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, const char *line, enum date_mode dmode, const char *encoding); @@ -80,7 +80,7 @@ void pp_title_line(enum cmit_fmt fmt, const char *subject, const char *after_subject, const char *encoding, - int plain_non_ascii); + int need_8bit_cte); void pp_remainder(enum cmit_fmt fmt, const char **msg_p, struct strbuf *sb, diff --git a/compat/snprintf.c b/compat/snprintf.c new file mode 100644 index 0000000000..dbfc2d6b6e --- /dev/null +++ b/compat/snprintf.c @@ -0,0 +1,40 @@ +#include "../git-compat-util.h" + +#undef vsnprintf +int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) +{ + char *s; + int ret; + + ret = vsnprintf(str, maxsize, format, ap); + if (ret != -1) + return ret; + + s = NULL; + if (maxsize < 128) + maxsize = 128; + + while (ret == -1) { + maxsize *= 4; + str = realloc(s, maxsize); + if (! str) + break; + s = str; + ret = vsnprintf(str, maxsize, format, ap); + } + free(s); + return ret; +} + +int git_snprintf(char *str, size_t maxsize, const char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = git_vsnprintf(str, maxsize, format, ap); + va_end(ap); + + return ret; +} + diff --git a/config.mak.in b/config.mak.in index ee6c33df03..7868dfd93a 100644 --- a/config.mak.in +++ b/config.mak.in @@ -46,3 +46,5 @@ NO_MKDTEMP=@NO_MKDTEMP@ NO_ICONV=@NO_ICONV@ OLD_ICONV=@OLD_ICONV@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ +FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@ +SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@ diff --git a/configure.ac b/configure.ac index 85d7ef570d..82584e9153 100644 --- a/configure.ac +++ b/configure.ac @@ -326,6 +326,60 @@ else NO_C99_FORMAT= fi AC_SUBST(NO_C99_FORMAT) +# +# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds +# when attempting to read from an fopen'ed directory. +AC_CACHE_CHECK([whether system succeeds to read fopen'ed directory], + [ac_cv_fread_reads_directories], +[ +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [[char c; + FILE *f = fopen(".", "r"); + return f && fread(&c, 1, 1, f)]])], + [ac_cv_fread_reads_directories=no], + [ac_cv_fread_reads_directories=yes]) +]) +if test $ac_cv_fread_reads_directories = yes; then + FREAD_READS_DIRECTORIES=UnfortunatelyYes +else + FREAD_READS_DIRECTORIES= +fi +AC_SUBST(FREAD_READS_DIRECTORIES) +# +# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf() +# or vsnprintf() return -1 instead of number of characters which would +# have been written to the final string if enough space had been available. +AC_CACHE_CHECK([whether snprintf() and/or vsnprintf() return bogus value], + [ac_cv_snprintf_returns_bogus], +[ +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT + #include "stdarg.h" + + int test_vsnprintf(char *str, size_t maxsize, const char *format, ...) + { + int ret; + va_list ap; + va_start(ap, format); + ret = vsnprintf(str, maxsize, format, ap); + va_end(ap); + return ret; + }], + [[char buf[6]; + if (test_vsnprintf(buf, 3, "%s", "12345") != 5 + || strcmp(buf, "12")) return 1; + if (snprintf(buf, 3, "%s", "12345") != 5 + || strcmp(buf, "12")) return 1]])], + [ac_cv_snprintf_returns_bogus=no], + [ac_cv_snprintf_returns_bogus=yes]) +]) +if test $ac_cv_snprintf_returns_bogus = yes; then + SNPRINTF_RETURNS_BOGUS=UnfortunatelyYes +else + SNPRINTF_RETURNS_BOGUS= +fi +AC_SUBST(SNPRINTF_RETURNS_BOGUS) ## Checks for library functions. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8f70e1efc1..5046f69934 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -70,22 +70,30 @@ __git_ps1 () local b if [ -d "$g/../.dotest" ] then - r="|AM/REBASE" + if test -f "$g/../.dotest/rebasing" + then + r="|REBASE" + elif test -f "$g/../.dotest/applying" + then + r="|AM" + else + r="|AM/REBASE" + fi b="$(git symbolic-ref HEAD 2>/dev/null)" elif [ -f "$g/.dotest-merge/interactive" ] then r="|REBASE-i" - b="$(cat $g/.dotest-merge/head-name)" + b="$(cat "$g/.dotest-merge/head-name")" elif [ -d "$g/.dotest-merge" ] then r="|REBASE-m" - b="$(cat $g/.dotest-merge/head-name)" + b="$(cat "$g/.dotest-merge/head-name")" elif [ -f "$g/MERGE_HEAD" ] then r="|MERGING" b="$(git symbolic-ref HEAD 2>/dev/null)" else - if [ -f $g/BISECT_LOG ] + if [ -f "$g/BISECT_LOG" ] then r="|BISECTING" fi @@ -93,7 +101,7 @@ __git_ps1 () then if ! b="$(git describe --exact-match HEAD 2>/dev/null)" then - b="$(cut -c1-7 $g/HEAD)..." + b="$(cut -c1-7 "$g/HEAD")..." fi fi fi @@ -113,13 +121,21 @@ __gitcomp () if [ $# -gt 2 ]; then cur="$3" fi - for c in $1; do - case "$c$4" in - --*=*) all="$all$c$4$s" ;; - *.) all="$all$c$4$s" ;; - *) all="$all$c$4 $s" ;; - esac - done + case "$cur" in + --*=) + COMPREPLY=() + return + ;; + *) + for c in $1; do + case "$c$4" in + --*=*) all="$all$c$4$s" ;; + *.) all="$all$c$4$s" ;; + *) all="$all$c$4 $s" ;; + esac + done + ;; + esac IFS=$s COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur")) return @@ -376,7 +392,6 @@ __git_commands () show-index) : plumbing;; ssh-*) : transport;; stripspace) : plumbing;; - svn) : import export;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; @@ -420,6 +435,22 @@ __git_aliased_command () done } +__git_find_subcommand () +{ + local word subcommand c=1 + + while [ $c -lt $COMP_CWORD ]; do + word="${COMP_WORDS[c]}" + for subcommand in $1; do + if [ "$subcommand" = "$word" ]; then + echo "$subcommand" + return + fi + done + c=$((++c)) + done +} + __git_whitespacelist="nowarn warn error error-all strip" _git_am () @@ -477,24 +508,14 @@ _git_add () _git_bisect () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - start|bad|good|reset|visualize|replay|log) - command="$i" - break - ;; - esac - c=$((++c)) - done - - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then - __gitcomp "start bad good reset visualize replay log" + local subcommands="start bad good reset visualize replay log" + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" return fi - case "$command" in + case "$subcommand" in bad|good|reset) __gitcomp "$(__git_refs)" ;; @@ -506,7 +527,33 @@ _git_bisect () _git_branch () { - __gitcomp "$(__git_refs)" + local i c=1 only_local_ref="n" has_r="n" + + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + -d|-m) only_local_ref="y" ;; + -r) has_r="y" ;; + esac + c=$((++c)) + done + + case "${COMP_WORDS[COMP_CWORD]}" in + --*=*) COMPREPLY=() ;; + --*) + __gitcomp " + --color --no-color --verbose --abbrev= --no-abbrev + --track --no-track + " + ;; + *) + if [ $only_local_ref = "y" -a $has_r = "n" ]; then + __gitcomp "$(__git_heads)" + else + __gitcomp "$(__git_refs)" + fi + ;; + esac } _git_bundle () @@ -802,8 +849,8 @@ _git_push () _git_rebase () { - local cur="${COMP_WORDS[COMP_CWORD]}" - if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then + local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" + if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then __gitcomp "--continue --skip --abort" return fi @@ -922,7 +969,6 @@ _git_config () core.sharedRepository core.warnAmbiguousRefs core.compression - core.legacyHeaders core.packedGitWindowSize core.packedGitLimit clean.requireForce @@ -999,21 +1045,13 @@ _git_config () _git_remote () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - add|rm|show|prune|update) command="$i"; break ;; - esac - c=$((++c)) - done - - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then - __gitcomp "add rm show prune update" + local subcommands="add rm show prune update" + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then return fi - case "$command" in + case "$subcommand" in rm|show|prune) __gitcomp "$(__git_remotes)" ;; @@ -1087,34 +1125,107 @@ _git_show () _git_stash () { - __gitcomp 'list show apply clear' + local subcommands='save list show apply clear drop pop create' + if [ -z "$(__git_find_subcommand "$subcommands")" ]; then + __gitcomp "$subcommands" + fi } _git_submodule () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - add|status|init|update) command="$i"; break ;; - esac - c=$((++c)) - done - - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + local subcommands="add status init update" + if [ -z "$(__git_find_subcommand "$subcommands")" ]; then local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) __gitcomp "--quiet --cached" ;; *) - __gitcomp "add status init update" + __gitcomp "$subcommands" ;; esac return fi } +_git_svn () +{ + local subcommands=" + init fetch clone rebase dcommit log find-rev + set-tree commit-diff info create-ignore propget + proplist show-ignore show-externals + " + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + local remote_opts="--username= --config-dir= --no-auth-cache" + local fc_opts=" + --follow-parent --authors-file= --repack= + --no-metadata --use-svm-props --use-svnsync-props + --log-window-size= --no-checkout --quiet + --repack-flags --user-log-author $remote_opts + " + local init_opts=" + --template= --shared= --trunk= --tags= + --branches= --stdlayout --minimize-url + --no-metadata --use-svm-props --use-svnsync-props + --rewrite-root= $remote_opts + " + local cmt_opts=" + --edit --rmdir --find-copies-harder --copy-similarity= + " + + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$subcommand,$cur" in + fetch,--*) + __gitcomp "--revision= --fetch-all $fc_opts" + ;; + clone,--*) + __gitcomp "--revision= $fc_opts $init_opts" + ;; + init,--*) + __gitcomp "$init_opts" + ;; + dcommit,--*) + __gitcomp " + --merge --strategy= --verbose --dry-run + --fetch-all --no-rebase $cmt_opts $fc_opts + " + ;; + set-tree,--*) + __gitcomp "--stdin $cmt_opts $fc_opts" + ;; + create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\ + show-externals,--*) + __gitcomp "--revision=" + ;; + log,--*) + __gitcomp " + --limit= --revision= --verbose --incremental + --oneline --show-commit --non-recursive + --authors-file= + " + ;; + rebase,--*) + __gitcomp " + --merge --verbose --strategy= --local + --fetch-all $fc_opts + " + ;; + commit-diff,--*) + __gitcomp "--message= --file= --revision= $cmt_opts" + ;; + info,--*) + __gitcomp "--url" + ;; + *) + COMPREPLY=() + ;; + esac + fi +} + _git_tag () { local i c=1 f=0 @@ -1164,15 +1275,18 @@ _git () c=$((++c)) done - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + if [ -z "$command" ]; then case "${COMP_WORDS[COMP_CWORD]}" in --*=*) COMPREPLY=() ;; --*) __gitcomp " + --paginate --no-pager --git-dir= --bare --version --exec-path + --work-tree= + --help " ;; *) __gitcomp "$(__git_commands) $(__git_aliases)" ;; @@ -1216,6 +1330,7 @@ _git () show-branch) _git_log ;; stash) _git_stash ;; submodule) _git_submodule ;; + svn) _git_svn ;; tag) _git_tag ;; whatchanged) _git_log ;; *) COMPREPLY=() ;; @@ -1266,6 +1381,7 @@ complete -o default -o nospace -F _git_shortlog git-shortlog complete -o default -o nospace -F _git_show git-show complete -o default -o nospace -F _git_stash git-stash complete -o default -o nospace -F _git_submodule git-submodule +complete -o default -o nospace -F _git_svn git-svn complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_tag git-tag complete -o default -o nospace -F _git_log git-whatchanged diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index c9268234a5..4fa853fae7 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1299,7 +1299,7 @@ Return the list of files that haven't been handled." (let (author-name author-email subject date msg) (with-temp-buffer (let ((coding-system (git-get-logoutput-coding-system))) - (git-call-process-env t nil "log" "-1" commit) + (git-call-process-env t nil "log" "-1" "--pretty=medium" commit) (goto-char (point-min)) (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t) (setq author-name (match-string 1)) diff --git a/git-remote.perl b/contrib/examples/git-remote.perl index b30ed734e7..b30ed734e7 100755 --- a/git-remote.perl +++ b/contrib/examples/git-remote.perl diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index be96600753..650ea34176 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -850,29 +850,32 @@ class P4Sync(Command): ## Should move this out, doesn't use SELF. def readP4Files(self, files): + filesForCommit = [] + filesToRead = [] + for f in files: + includeFile = True for val in self.clientSpecDirs: if f['path'].startswith(val[0]): - if val[1] > 0: - f['include'] = True - else: - f['include'] = False + if val[1] <= 0: + includeFile = False break - files = [f for f in files - if f['action'] != 'delete' and - (f.has_key('include') == False or f['include'] == True)] + if includeFile: + filesForCommit.append(f) + if f['action'] != 'delete': + filesToRead.append(f) - if not files: - return [] + filedata = [] + if len(filesToRead) > 0: + filedata = p4CmdList('-x - print', + stdin='\n'.join(['%s#%s' % (f['path'], f['rev']) + for f in filesToRead]), + stdin_mode='w+') - filedata = p4CmdList('-x - print', - stdin='\n'.join(['%s#%s' % (f['path'], f['rev']) - for f in files]), - stdin_mode='w+') - if "p4ExitCode" in filedata[0]: - die("Problems executing p4. Error: [%d]." - % (filedata[0]['p4ExitCode'])); + if "p4ExitCode" in filedata[0]: + die("Problems executing p4. Error: [%d]." + % (filedata[0]['p4ExitCode'])); j = 0; contents = {} @@ -896,10 +899,12 @@ class P4Sync(Command): contents[stat['depotFile']] = text - for f in files: - assert not f.has_key('data') - f['data'] = contents[f['path']] - return files + for f in filesForCommit: + path = f['path'] + if contents.has_key(path): + f['data'] = contents[path] + + return filesForCommit def commit(self, details, files, branch, branchPrefixes, parent = ""): epoch = details["time"] diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 77c88ebf1f..62a740c482 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -567,7 +567,7 @@ generate_general_email() echo "" if [ "$newrev_type" = "commit" ]; then echo $LOGBEGIN - git show --no-color --root -s $newrev + git show --no-color --root -s --pretty=medium $newrev echo $LOGEND else # What can we do here? The tag marks an object that is not diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir index 2838546d16..7959eab902 100755 --- a/contrib/workdir/git-new-workdir +++ b/contrib/workdir/git-new-workdir @@ -63,7 +63,7 @@ mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!" # create the links to the original repo. explictly exclude index, HEAD and # logs/HEAD from the list since they are purely related to the current working # directory, and should not be shared. -for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache +for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn do case $x in */*) diff --git a/diff-lib.c b/diff-lib.c index 4581b594d0..52dbac34a4 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -600,8 +600,7 @@ static void mark_merge_entries(void) */ static void do_oneway_diff(struct unpack_trees_options *o, struct cache_entry *idx, - struct cache_entry *tree, - int idx_pos, int idx_nr) + struct cache_entry *tree) { struct rev_info *revs = o->unpack_data; int match_missing, cached; @@ -642,32 +641,19 @@ static void do_oneway_diff(struct unpack_trees_options *o, show_modified(revs, tree, idx, 1, cached, match_missing); } -/* - * Count how many index entries go with the first one - */ -static inline int count_skip(const struct cache_entry *src, int pos) +static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) { - int skip = 1; - - /* We can only have multiple entries if the first one is not stage-0 */ - if (ce_stage(src)) { - struct cache_entry **p = active_cache + pos; - int namelen = ce_namelen(src); - - for (;;) { - const struct cache_entry *ce; - pos++; - if (pos >= active_nr) - break; - ce = *++p; - if (ce_namelen(ce) != namelen) - break; - if (memcmp(ce->name, src->name, namelen)) - break; - skip++; - } + int len = ce_namelen(ce); + const struct index_state *index = o->src_index; + + while (o->pos < index->cache_nr) { + struct cache_entry *next = index->cache[o->pos]; + if (len != ce_namelen(next)) + break; + if (memcmp(ce->name, next->name, len)) + break; + o->pos++; } - return skip; } /* @@ -685,17 +671,14 @@ static inline int count_skip(const struct cache_entry *src, int pos) * the fairly complex unpack_trees() semantic requirements, including * the skipping, the path matching, the type conflict cases etc. */ -static int oneway_diff(struct cache_entry **src, - struct unpack_trees_options *o, - int index_pos) +static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) { - int skip = 0; struct cache_entry *idx = src[0]; struct cache_entry *tree = src[1]; struct rev_info *revs = o->unpack_data; - if (index_pos >= 0) - skip = count_skip(idx, index_pos); + if (idx && ce_stage(idx)) + skip_same_name(idx, o); /* * Unpack-trees generates a DF/conflict entry if @@ -707,9 +690,9 @@ static int oneway_diff(struct cache_entry **src, tree = NULL; if (ce_path_match(idx ? idx : tree, revs->prune_data)) - do_oneway_diff(o, idx, tree, index_pos, skip); + do_oneway_diff(o, idx, tree); - return skip; + return 0; } int run_diff_index(struct rev_info *revs, int cached) @@ -734,6 +717,8 @@ int run_diff_index(struct rev_info *revs, int cached) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = revs; + opts.src_index = &the_index; + opts.dst_index = NULL; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -787,6 +772,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = &revs; + opts.src_index = &the_index; + opts.dst_index = &the_index; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -256,40 +256,41 @@ static int count_lines(const char *data, int size) return count; } -static void print_line_count(int count) +static void print_line_count(FILE *file, int count) { switch (count) { case 0: - printf("0,0"); + fprintf(file, "0,0"); break; case 1: - printf("1"); + fprintf(file, "1"); break; default: - printf("1,%d", count); + fprintf(file, "1,%d", count); break; } } -static void copy_file_with_prefix(int prefix, const char *data, int size, +static void copy_file_with_prefix(FILE *file, + int prefix, const char *data, int size, const char *set, const char *reset) { int ch, nl_just_seen = 1; while (0 < size--) { ch = *data++; if (nl_just_seen) { - fputs(set, stdout); - putchar(prefix); + fputs(set, file); + putc(prefix, file); } if (ch == '\n') { nl_just_seen = 1; - fputs(reset, stdout); + fputs(reset, file); } else nl_just_seen = 0; - putchar(ch); + putc(ch, file); } if (!nl_just_seen) - printf("%s\n\\ No newline at end of file\n", reset); + fprintf(file, "%s\n\\ No newline at end of file\n", reset); } static void emit_rewrite_diff(const char *name_a, @@ -322,17 +323,18 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - printf("%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -", - metainfo, a_name.buf, name_a_tab, reset, - metainfo, b_name.buf, name_b_tab, reset, fraginfo); - print_line_count(lc_a); - printf(" +"); - print_line_count(lc_b); - printf(" @@%s\n", reset); + fprintf(o->file, + "%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -", + metainfo, a_name.buf, name_a_tab, reset, + metainfo, b_name.buf, name_b_tab, reset, fraginfo); + print_line_count(o->file, lc_a); + fprintf(o->file, " +"); + print_line_count(o->file, lc_b); + fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix('-', one->data, one->size, old, reset); + copy_file_with_prefix(o->file, '-', one->data, one->size, old, reset); if (lc_b) - copy_file_with_prefix('+', two->data, two->size, new, reset); + copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); } static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) @@ -372,9 +374,10 @@ static void diff_words_append(char *line, unsigned long len, struct diff_words_data { struct xdiff_emit_state xm; struct diff_words_buffer minus, plus; + FILE *file; }; -static void print_word(struct diff_words_buffer *buffer, int len, int color, +static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color, int suppress_newline) { const char *ptr; @@ -391,15 +394,15 @@ static void print_word(struct diff_words_buffer *buffer, int len, int color, len--; } - fputs(diff_get_color(1, color), stdout); - fwrite(ptr, len, 1, stdout); - fputs(diff_get_color(1, DIFF_RESET), stdout); + fputs(diff_get_color(1, color), file); + fwrite(ptr, len, 1, file); + fputs(diff_get_color(1, DIFF_RESET), file); if (eol) { if (suppress_newline) buffer->suppressed_newline = 1; else - putchar('\n'); + putc('\n', file); } } @@ -409,20 +412,23 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) if (diff_words->minus.suppressed_newline) { if (line[0] != '+') - putchar('\n'); + putc('\n', diff_words->file); diff_words->minus.suppressed_newline = 0; } len--; switch (line[0]) { case '-': - print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1); + print_word(diff_words->file, + &diff_words->minus, len, DIFF_FILE_OLD, 1); break; case '+': - print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0); + print_word(diff_words->file, + &diff_words->plus, len, DIFF_FILE_NEW, 0); break; case ' ': - print_word(&diff_words->plus, len, DIFF_PLAIN, 0); + print_word(diff_words->file, + &diff_words->plus, len, DIFF_PLAIN, 0); diff_words->minus.current += len; break; } @@ -466,7 +472,7 @@ static void diff_words_show(struct diff_words_data *diff_words) diff_words->minus.text.size = diff_words->plus.text.size = 0; if (diff_words->minus.suppressed_newline) { - putchar('\n'); + putc('\n', diff_words->file); diff_words->minus.suppressed_newline = 0; } } @@ -481,6 +487,7 @@ struct emit_callback { const char **label_path; struct diff_words_data *diff_words; int *found_changesp; + FILE *file; }; static void free_diff_words_data(struct emit_callback *ecbdata) @@ -505,11 +512,11 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(const char *set, const char *reset, const char *line, int len) +static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) { - fputs(set, stdout); - fwrite(line, len, 1, stdout); - fputs(reset, stdout); + fputs(set, file); + fwrite(line, len, 1, file); + fputs(reset, file); } static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) @@ -518,13 +525,13 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line(set, reset, line, len); + emit_line(ecbdata->file, set, reset, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(set, reset, line, ecbdata->nparents); + emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); (void)check_and_emit_line(line + ecbdata->nparents, len - ecbdata->nparents, ecbdata->ws_rule, - stdout, set, reset, ws); + ecbdata->file, set, reset, ws); } } @@ -563,10 +570,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - printf("%s--- %s%s%s\n", - meta, ecbdata->label_path[0], reset, name_a_tab); - printf("%s+++ %s%s%s\n", - meta, ecbdata->label_path[1], reset, name_b_tab); + fprintf(ecbdata->file, "%s--- %s%s%s\n", + meta, ecbdata->label_path[0], reset, name_a_tab); + fprintf(ecbdata->file, "%s+++ %s%s%s\n", + meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -578,15 +585,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (2 <= i && i < len && line[i] == ' ') { ecbdata->nparents = i - 1; len = sane_truncate_line(ecbdata, line, len); - emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), + emit_line(ecbdata->file, + diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); if (line[len-1] != '\n') - putchar('\n'); + putc('\n', ecbdata->file); return; } if (len < ecbdata->nparents) { - emit_line(reset, reset, line, len); + emit_line(ecbdata->file, reset, reset, line, len); return; } @@ -609,7 +617,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) diff_words_show(ecbdata->diff_words); line++; len--; - emit_line(plain, reset, line, len); + emit_line(ecbdata->file, plain, reset, line, len); return; } for (i = 0; i < ecbdata->nparents && len; i++) { @@ -620,7 +628,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) } if (color != DIFF_FILE_NEW) { - emit_line(diff_get_color(ecbdata->color_diff, color), + emit_line(ecbdata->file, + diff_get_color(ecbdata->color_diff, color), reset, line, len); return; } @@ -759,20 +768,21 @@ static int scale_linear(int it, int width, int max_change) return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1); } -static void show_name(const char *prefix, const char *name, int len, +static void show_name(FILE *file, + const char *prefix, const char *name, int len, const char *reset, const char *set) { - printf(" %s%s%-*s%s |", set, prefix, len, name, reset); + fprintf(file, " %s%s%-*s%s |", set, prefix, len, name, reset); } -static void show_graph(char ch, int cnt, const char *set, const char *reset) +static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset) { if (cnt <= 0) return; - printf("%s", set); + fprintf(file, "%s", set); while (cnt--) - putchar(ch); - printf("%s", reset); + putc(ch, file); + fprintf(file, "%s", reset); } static void fill_print_name(struct diffstat_file *file) @@ -877,18 +887,18 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } if (data->files[i]->is_binary) { - show_name(prefix, name, len, reset, set); - printf(" Bin "); - printf("%s%d%s", del_c, deleted, reset); - printf(" -> "); - printf("%s%d%s", add_c, added, reset); - printf(" bytes"); - printf("\n"); + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, " Bin "); + fprintf(options->file, "%s%d%s", del_c, deleted, reset); + fprintf(options->file, " -> "); + fprintf(options->file, "%s%d%s", add_c, added, reset); + fprintf(options->file, " bytes"); + fprintf(options->file, "\n"); continue; } else if (data->files[i]->is_unmerged) { - show_name(prefix, name, len, reset, set); - printf(" Unmerged\n"); + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, " Unmerged\n"); continue; } else if (!data->files[i]->is_renamed && @@ -911,17 +921,18 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) del = scale_linear(del, width, max_change); total = add + del; } - show_name(prefix, name, len, reset, set); - printf("%5d ", added + deleted); - show_graph('+', add, add_c, reset); - show_graph('-', del, del_c, reset); - putchar('\n'); - } - printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n", + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, "%5d ", added + deleted); + show_graph(options->file, '+', add, add_c, reset); + show_graph(options->file, '-', del, del_c, reset); + fprintf(options->file, "\n"); + } + fprintf(options->file, + "%s %d files changed, %d insertions(+), %d deletions(-)%s\n", set, total_files, adds, dels, reset); } -static void show_shortstats(struct diffstat_t* data) +static void show_shortstats(struct diffstat_t* data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; @@ -942,7 +953,7 @@ static void show_shortstats(struct diffstat_t* data) } } } - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", + fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -957,24 +968,25 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) struct diffstat_file *file = data->files[i]; if (file->is_binary) - printf("-\t-\t"); + fprintf(options->file, "-\t-\t"); else - printf("%d\t%d\t", file->added, file->deleted); + fprintf(options->file, + "%d\t%d\t", file->added, file->deleted); if (options->line_termination) { fill_print_name(file); if (!file->is_renamed) - write_name_quoted(file->name, stdout, + write_name_quoted(file->name, options->file, options->line_termination); else { - fputs(file->print_name, stdout); - putchar(options->line_termination); + fputs(file->print_name, options->file); + putc(options->line_termination, options->file); } } else { if (file->is_renamed) { - putchar('\0'); - write_name_quoted(file->from_name, stdout, '\0'); + putc('\0', options->file); + write_name_quoted(file->from_name, options->file, '\0'); } - write_name_quoted(file->name, stdout, '\0'); + write_name_quoted(file->name, options->file, '\0'); } } } @@ -984,7 +996,7 @@ struct diffstat_dir { int nr, percent, cumulative; }; -static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) { unsigned long this_dir = 0; unsigned int sources = 0; @@ -1002,7 +1014,7 @@ static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, cons slash = strchr(f->name + baselen, '/'); if (slash) { int newbaselen = slash + 1 - f->name; - this = gather_dirstat(dir, changed, f->name, newbaselen); + this = gather_dirstat(file, dir, changed, f->name, newbaselen); sources++; } else { if (f->is_unmerged || f->is_binary) @@ -1027,7 +1039,7 @@ static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, cons if (permille) { int percent = permille / 10; if (percent >= dir->percent) { - printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1060,7 +1072,7 @@ static void show_dirstat(struct diffstat_t *data, struct diff_options *options) dir.nr = data->nr; dir.percent = options->dirstat_percent; dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; - gather_dirstat(&dir, changed, "", 0); + gather_dirstat(options->file, &dir, changed, "", 0); } static void free_diffstat_info(struct diffstat_t *diffstat) @@ -1083,6 +1095,7 @@ struct checkdiff_t { int lineno, color_diff; unsigned ws_rule; unsigned status; + FILE *file; }; static void checkdiff_consume(void *priv, char *line, unsigned long len) @@ -1100,11 +1113,11 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (!data->status) return; err = whitespace_error_string(data->status); - printf("%s:%d: %s.\n", data->filename, data->lineno, err); + fprintf(data->file, "%s:%d: %s.\n", data->filename, data->lineno, err); free(err); - emit_line(set, reset, line, 1); + emit_line(data->file, set, reset, line, 1); (void)check_and_emit_line(line + 1, len - 1, data->ws_rule, - stdout, set, reset, ws); + data->file, set, reset, ws); } else if (line[0] == ' ') data->lineno++; else if (line[0] == '@') { @@ -1140,7 +1153,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -1169,13 +1182,13 @@ static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) } if (delta && delta_size < deflate_size) { - printf("delta %lu\n", orig_size); + fprintf(file, "delta %lu\n", orig_size); free(deflated); data = delta; data_size = delta_size; } else { - printf("literal %lu\n", two->size); + fprintf(file, "literal %lu\n", two->size); free(delta); data = deflated; data_size = deflate_size; @@ -1193,17 +1206,18 @@ static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) line[0] = bytes - 26 + 'a' - 1; encode_85(line + 1, cp, bytes); cp = (char *) cp + bytes; - puts(line); + fputs(line, file); + fputc('\n', file); } - printf("\n"); + fprintf(file, "\n"); free(data); } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two) { - printf("GIT binary patch\n"); - emit_binary_diff_body(one, two); - emit_binary_diff_body(two, one); + fprintf(file, "GIT binary patch\n"); + emit_binary_diff_body(file, one, two); + emit_binary_diff_body(file, two, one); } static void setup_diff_attr_check(struct git_attr_check *check) @@ -1334,25 +1348,25 @@ static void builtin_diff(const char *name_a, b_two = quote_two(o->b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - printf("%snew file mode %06o%s\n", set, two->mode, reset); + fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - printf("%sdeleted file mode %06o%s\n", set, one->mode, reset); + fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - printf("%sold mode %06o%s\n", set, one->mode, reset); - printf("%snew mode %06o%s\n", set, two->mode, reset); + fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset); + fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); /* * we do not run diff between different kind * of objects. @@ -1376,10 +1390,10 @@ static void builtin_diff(const char *name_a, !memcmp(mf1.ptr, mf2.ptr, mf1.size)) goto free_ab_and_return; if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(&mf1, &mf2); + emit_binary_diff(o->file, &mf1, &mf2); else - printf("Binary files %s and %s differ\n", - lbl[0], lbl[1]); + fprintf(o->file, "Binary files %s and %s differ\n", + lbl[0], lbl[1]); o->found_changes = 1; } else { @@ -1401,6 +1415,7 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1415,9 +1430,11 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) + if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); + ecbdata.diff_words->file = o->file; + } xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); @@ -1496,6 +1513,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.lineno = 0; data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); data.ws_rule = whitespace_rule(attr_path); + data.file = o->file; if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1966,7 +1984,7 @@ static void run_diff_cmd(const char *pgm, builtin_diff(name, other ? other : name, one, two, xfrm_msg, o, complete_rewrite); else - printf("* Unmerged path %s\n", name); + fprintf(o->file, "* Unmerged path %s\n", name); } static void diff_fill_sha1_info(struct diff_filespec *one) @@ -2157,6 +2175,9 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) void diff_setup(struct diff_options *options) { memset(options, 0, sizeof(*options)); + + options->file = stdout; + options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; @@ -2470,7 +2491,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->b_prefix = arg + 13; else if (!strcmp(arg, "--no-prefix")) options->a_prefix = options->b_prefix = ""; - else + else if (!prefixcmp(arg, "--output=")) { + options->file = fopen(arg + strlen("--output="), "w"); + options->close_file = 1; + } else return 0; return 1; } @@ -2599,15 +2623,15 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) int inter_name_termination = line_termination ? '\t' : '\0'; 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)); + fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, opt->abbrev)); + fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); } if (p->score) { - printf("%c%03d%c", p->status, similarity_index(p), - inter_name_termination); + fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p), + inter_name_termination); } else { - printf("%c%c", p->status, inter_name_termination); + fprintf(opt->file, "%c%c", p->status, inter_name_termination); } if (p->status == DIFF_STATUS_COPIED || @@ -2616,14 +2640,14 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) name_a = p->one->path; name_b = p->two->path; strip_prefix(opt->prefix_length, &name_a, &name_b); - write_name_quoted(name_a, stdout, inter_name_termination); - write_name_quoted(name_b, stdout, line_termination); + write_name_quoted(name_a, opt->file, inter_name_termination); + write_name_quoted(name_b, opt->file, line_termination); } else { const char *name_a, *name_b; name_a = p->one->mode ? p->one->path : p->two->path; name_b = NULL; strip_prefix(opt->prefix_length, &name_a, &name_b); - write_name_quoted(name_a, stdout, line_termination); + write_name_quoted(name_a, opt->file, line_termination); } } @@ -2825,62 +2849,62 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) name_a = p->two->path; name_b = NULL; strip_prefix(opt->prefix_length, &name_a, &name_b); - write_name_quoted(name_a, stdout, opt->line_termination); + write_name_quoted(name_a, opt->file, opt->line_termination); } } -static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs) +static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_filespec *fs) { if (fs->mode) - printf(" %s mode %06o ", newdelete, fs->mode); + fprintf(file, " %s mode %06o ", newdelete, fs->mode); else - printf(" %s ", newdelete); - write_name_quoted(fs->path, stdout, '\n'); + fprintf(file, " %s ", newdelete); + write_name_quoted(fs->path, file, '\n'); } -static void show_mode_change(struct diff_filepair *p, int show_name) +static void show_mode_change(FILE *file, 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, + fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode, show_name ? ' ' : '\n'); if (show_name) { - write_name_quoted(p->two->path, stdout, '\n'); + write_name_quoted(p->two->path, file, '\n'); } } } -static void show_rename_copy(const char *renamecopy, struct diff_filepair *p) +static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p) { char *names = pprint_rename(p->one->path, p->two->path); - printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); + fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); - show_mode_change(p, 0); + show_mode_change(file, p, 0); } -static void diff_summary(struct diff_filepair *p) +static void diff_summary(FILE *file, struct diff_filepair *p) { switch(p->status) { case DIFF_STATUS_DELETED: - show_file_mode_name("delete", p->one); + show_file_mode_name(file, "delete", p->one); break; case DIFF_STATUS_ADDED: - show_file_mode_name("create", p->two); + show_file_mode_name(file, "create", p->two); break; case DIFF_STATUS_COPIED: - show_rename_copy("copy", p); + show_rename_copy(file, "copy", p); break; case DIFF_STATUS_RENAMED: - show_rename_copy("rename", p); + show_rename_copy(file, "rename", p); break; default: if (p->score) { - fputs(" rewrite ", stdout); - write_name_quoted(p->two->path, stdout, ' '); - printf("(%d%%)\n", similarity_index(p)); + fputs(" rewrite ", file); + write_name_quoted(p->two->path, file, ' '); + fprintf(file, "(%d%%)\n", similarity_index(p)); } - show_mode_change(p, !p->score); + show_mode_change(file, p, !p->score); break; } } @@ -3088,14 +3112,14 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_DIFFSTAT) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) - show_shortstats(&diffstat); + show_shortstats(&diffstat, options); free_diffstat_info(&diffstat); separator++; } if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { for (i = 0; i < q->nr; i++) - diff_summary(q->queue[i]); + diff_summary(options->file, q->queue[i]); separator++; } @@ -3103,9 +3127,9 @@ void diff_flush(struct diff_options *options) if (separator) { if (options->stat_sep) { /* attach patch instead of inline */ - fputs(options->stat_sep, stdout); + fputs(options->stat_sep, options->file); } else { - putchar(options->line_termination); + putc(options->line_termination, options->file); } } @@ -3125,6 +3149,8 @@ free_queue: free(q->queue); q->queue = NULL; q->nr = q->alloc = 0; + if (options->close_file) + fclose(options->file); } static void diffcore_apply_filter(const char *filter) @@ -98,6 +98,9 @@ struct diff_options { /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; + FILE *file; + int close_file; + int nr_paths; const char **paths; int *pathlens; diff --git a/fast-import.c b/fast-import.c index 7f197d5e36..655913ddb2 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2291,7 +2291,8 @@ static void cmd_reset_branch(void) else b = new_branch(sp); read_next_command(); - if (!cmd_from(b) && command_buf.len > 0) + cmd_from(b); + if (command_buf.len > 0) unread_command_buf = 1; } diff --git a/fetch-pack.h b/fetch-pack.h index 8d35ef60bf..8bd9c32561 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -12,7 +12,8 @@ struct fetch_pack_args use_thin_pack:1, fetch_all:1, verbose:1, - no_progress:1; + no_progress:1, + include_tag:1; }; struct ref *fetch_pack(struct fetch_pack_args *args, @@ -155,8 +155,6 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) o_mode = 0; o_name = NULL; o_sha1 = NULL; - if (!desc.size) - return error_func(&item->object, FSCK_ERROR, "empty tree"); while (desc.size) { unsigned mode; @@ -9,7 +9,7 @@ git-am [options] <mbox>|<Maildir>... git-am [options] --resolved git-am [options] --skip -- -d,dotest= use <dir> and not .dotest +d,dotest= (removed -- do not use) i,interactive run interactively b,binary pass --allo-binary-replacement to git-apply 3,3way allow fall back on 3way merging if needed @@ -21,9 +21,11 @@ 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" +skip skip the current patch +rebasing (internal use for git-rebase)" . git-sh-setup +prefix=$(git rev-parse --show-prefix) set_reflog_action am require_work_tree cd_to_toplevel @@ -49,10 +51,6 @@ stop_here_user_resolve () { then cmdline="$cmdline -3" fi - if test '.dotest' != "$dotest" - then - cmdline="$cmdline -d=$dotest" - fi echo "When you have resolved this problem run \"$cmdline --resolved\"." echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"." @@ -124,7 +122,8 @@ reread_subject () { } prec=4 -dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= +dotest=".dotest" +sign= utf8=t keep= skip= interactive= resolved= binary= rebasing= resolvemsg= resume= git_apply_opt= @@ -149,8 +148,11 @@ do resolved=t ;; --skip) skip=t ;; + --rebasing) + rebasing=t threeway=t keep=t binary=t ;; -d|--dotest) - shift; dotest=$1;; + die "-d option is no longer supported. Do not use." + ;; --resolvemsg) shift; resolvemsg=$1 ;; --whitespace) @@ -186,7 +188,7 @@ then 0,) # No file input but without resume parameters; catch # user error to feed us a patch from standard input - # when there is already .dotest. This is somewhat + # when there is already $dotest. This is somewhat # unreliable -- stdin could be /dev/null for example # and the caller did not intend to feed us a patch but # wanted to continue unattended. @@ -206,6 +208,24 @@ else # Start afresh. mkdir -p "$dotest" || exit + if test -n "$prefix" && test $# != 0 + then + first=t + for arg + do + test -n "$first" && { + set x + first= + } + case "$arg" in + /*) + set "$@" "$arg" ;; + *) + set "$@" "$prefix$arg" ;; + esac + done + shift + fi git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" || { rm -fr "$dotest" exit 1 @@ -220,6 +240,12 @@ else echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" echo 1 >"$dotest/next" + if test -n "$rebasing" + then + : >"$dotest/rebasing" + else + : >"$dotest/applying" + fi fi case "$resolved" in diff --git a/git-compat-util.h b/git-compat-util.h index 2a40703c85..a18235e6d0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -68,6 +68,7 @@ #include <sys/poll.h> #include <sys/socket.h> #include <sys/ioctl.h> +#include <utime.h> #ifndef NO_SYS_SELECT_H #include <sys/select.h> #endif @@ -209,6 +210,15 @@ void *gitmemmem(const void *haystack, size_t haystacklen, extern FILE *git_fopen(const char*, const char*); #endif +#ifdef SNPRINTF_RETURNS_BOGUS +#define snprintf git_snprintf +extern int git_snprintf(char *str, size_t maxsize, + const char *format, ...); +#define vsnprintf git_vsnprintf +extern int git_vsnprintf(char *str, size_t maxsize, + const char *format, va_list ap); +#endif + #ifdef __GLIBC_PREREQ #if __GLIBC_PREREQ(2, 1) #define HAVE_STRCHRNUL @@ -437,4 +447,10 @@ void git_qsort(void *base, size_t nmemb, size_t size, #define qsort git_qsort #endif +#ifndef DIR_HAS_BSD_GROUP_SEMANTICS +# define FORCE_DIR_SET_GID S_ISGID +#else +# define FORCE_DIR_SET_GID 0 +#endif + #endif diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 47f116f37e..95c5eec51e 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -735,7 +735,7 @@ sub commit { next unless $logmsg =~ $rx && $1; my $mparent = $1 eq 'HEAD' ? $opt_o : $1; if (my $sha1 = get_headref("$remote/$mparent")) { - push @commit_args, '-p', $mparent; + push @commit_args, '-p', "$remote/$mparent"; print "Merge parent branch: $mparent\n" if $opt_v; } } diff --git a/git-cvsserver.perl b/git-cvsserver.perl index afe3d0b7fe..7f632af20d 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2556,7 +2556,7 @@ sub update if ($base) { my @merged; # print "want to log between $base $parent \n"; - open(GITLOG, '-|', 'git-log', "$base..$parent") + open(GITLOG, '-|', 'git-log', '--pretty=medium', "$base..$parent") or die "Cannot call git-log: $!"; my $mergedhash; while (<GITLOG>) { diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 49e13f0bb1..010353ad82 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -252,7 +252,16 @@ while read commit parents; do git read-tree -i -m $commit ;; *) - git read-tree -i -m $commit:"$filter_subdir" + # The commit may not have the subdirectory at all + err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || { + if ! git rev-parse --verify $commit:"$filter_subdir" 2>/dev/null + then + rm -f "$GIT_INDEX_FILE" + else + echo >&2 "$err" + false + fi + } esac || die "Could not initialize the index" GIT_COMMIT=$commit diff --git a/git-gui/Makefile b/git-gui/Makefile index 01e0a46ba5..b19fb2d64e 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -221,7 +221,7 @@ ifdef NO_MSGFMT MSGFMT ?= $(TCL_PATH) po/po2msg.sh else MSGFMT ?= msgfmt - ifeq ($(shell $(MSGFMT) >/dev/null 2>&1 || echo $$?),127) + ifneq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null; echo $$?),0) MSGFMT := $(TCL_PATH) po/po2msg.sh endif endif diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 238a2393ff..3a58cd2c6b 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -611,6 +611,7 @@ 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.commitmsgwidth) 75 set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] @@ -2289,8 +2290,9 @@ pack .vpane -anchor n -side top -fill both -expand 1 # frame .vpane.files.index -height 100 -width 200 label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \ - -background lightgreen -text $ui_index -background white -borderwidth 0 \ + -background lightgreen -foreground black +text $ui_index -background white -foreground black \ + -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ -cursor $cursor_ptr \ @@ -2308,8 +2310,9 @@ pack $ui_index -side left -fill both -expand 1 # frame .vpane.files.workdir -height 100 -width 200 label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ - -background lightsalmon -text $ui_workdir -background white -borderwidth 0 \ + -background lightsalmon -foreground black +text $ui_workdir -background white -foreground black \ + -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ -cursor $cursor_ptr \ @@ -2416,12 +2419,13 @@ pack $ui_coml -side left -fill x pack .vpane.lower.commarea.buffer.header.amend -side right pack .vpane.lower.commarea.buffer.header.new -side right -text $ui_comm -background white -borderwidth 1 \ +text $ui_comm -background white -foreground black \ + -borderwidth 1 \ -undo true \ -maxundo 20 \ -autoseparators true \ -relief sunken \ - -width 75 -height 9 -wrap none \ + -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ -font font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} scrollbar .vpane.lower.commarea.buffer.sby \ @@ -2493,15 +2497,18 @@ trace add variable current_diff_path write trace_current_diff_path frame .vpane.lower.diff.header -background gold label .vpane.lower.diff.header.status \ -background gold \ + -foreground black \ -width $max_status_desc \ -anchor w \ -justify left label .vpane.lower.diff.header.file \ -background gold \ + -foreground black \ -anchor w \ -justify left label .vpane.lower.diff.header.path \ -background gold \ + -foreground black \ -anchor w \ -justify left pack .vpane.lower.diff.header.status -side left @@ -2525,7 +2532,8 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t -text $ui_diff -background white -borderwidth 0 \ +text $ui_diff -background white -foreground black \ + -borderwidth 0 \ -width 80 -height 15 -wrap none \ -font font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 00ecf21333..92fac1bad4 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -80,6 +80,7 @@ constructor new {i_commit i_path} { label $w.header.commit_l \ -text [mc "Commit:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_back $w.header.commit_b @@ -89,6 +90,7 @@ constructor new {i_commit i_path} { -relief flat \ -state disabled \ -background gold \ + -foreground black \ -activebackground gold bind $w_back <Button-1> " if {\[$w_back cget -state\] eq {normal}} { @@ -98,16 +100,19 @@ constructor new {i_commit i_path} { label $w.header.commit \ -textvariable @commit \ -background gold \ + -foreground black \ -anchor w \ -justify left label $w.header.path_l \ -text [mc "File:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_path $w.header.path label $w_path \ -background gold \ + -foreground black \ -anchor w \ -justify left pack $w.header.commit_l -side left @@ -135,7 +140,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -148,7 +155,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -166,7 +175,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -184,7 +195,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -213,7 +226,9 @@ constructor new {i_commit i_path} { set w_cviewer $w.file_pane.cm.t text $w_cviewer \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 10 \ diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 53d5a62816..ab470d1264 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -39,7 +39,8 @@ constructor new {commit {path {}}} { frame $w.list set w_list $w.list.l - text $w_list -background white -borderwidth 0 \ + text $w_list -background white -foreground black \ + -borderwidth 0 \ -cursor $cursor_ptr \ -state disabled \ -wrap none \ diff --git a/git-gui/lib/choose_font.tcl b/git-gui/lib/choose_font.tcl index 0c4051b375..56443b042c 100644 --- a/git-gui/lib/choose_font.tcl +++ b/git-gui/lib/choose_font.tcl @@ -55,6 +55,7 @@ constructor pick {path title a_family a_size} { set w_family $w.inner.family.v text $w_family \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -cursor $::cursor_ptr \ @@ -92,6 +93,7 @@ constructor pick {path title a_family a_size} { set w_example $w.example.t text $w_example \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -height 3 \ diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index 5597188d80..c112464ec3 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -46,7 +46,9 @@ method _init {} { -justify left \ -font font_uibold text $w_t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -wrap none \ @@ -180,7 +182,8 @@ method done {ok} { if {$ok} { if {[winfo exists $w.m.s]} { bind $w.m.s <Destroy> [list delete_this $this] - $w.m.s conf -background green -text [mc "Success"] + $w.m.s conf -background green -foreground black \ + -text [mc "Success"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok @@ -193,7 +196,8 @@ method done {ok} { _init $this } bind $w.m.s <Destroy> [list delete_this $this] - $w.m.s conf -background red -text [mc "Error: Command Failed"] + $w.m.s conf -background red -foreground black \ + -text [mc "Error: Command Failed"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 8c27678e3a..75650157e5 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -80,7 +80,9 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { -justify left \ -font font_uibold text $w.m.t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -font font_diff \ diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index ea80df0092..9270512582 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -124,6 +124,7 @@ proc do_options {} { {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"}} + {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} } { set type [lindex $option 0] diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po index 621c9479b2..f8697216f7 100644 --- a/git-gui/po/zh_cn.po +++ b/git-gui/po/zh_cn.po @@ -3,46 +3,63 @@ # This file is distributed under the same license as the git-gui package. # Xudong Guan <xudong.guan@gmail.com>, 2007. # +# Please use the following translation throughout the file for consistence: +# +# repository 版本库 +# commit 提交 +# revision 版本 +# branch 分支 +# tag 标签 +# annotation 标注 +# merge 合并 +# fast forward 快速合并(??) +# stage 缓存 (译自 index/cache) +# amend 修正 +# reset 复位 +# +# 2008-01-06 Eric Miao <eric.y.miao@gmail.com> +# FIXME: checkout 的标准翻译 +# #, fuzzy msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"POT-Creation-Date: 2007-11-24 10:36+0100\n" "PO-Revision-Date: 2007-07-21 01:23-0700\n" -"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n" +"Last-Translator: Eric Miao <eric.y.miao@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 +#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714 +#: git-gui.sh:733 msgid "git-gui: fatal error" -msgstr "" +msgstr "git-gui: 致命错误" -#: git-gui.sh:595 +#: git-gui.sh:565 #, tcl-format msgid "Invalid font specified in %s:" -msgstr "" +msgstr "%s 中指定的字体无效:" -#: git-gui.sh:620 +#: git-gui.sh:590 msgid "Main Font" -msgstr "" +msgstr "主要字体" -#: git-gui.sh:621 +#: git-gui.sh:591 msgid "Diff/Console Font" -msgstr "" +msgstr "Diff/控制终端字体" -#: git-gui.sh:635 +#: git-gui.sh:605 msgid "Cannot find git in PATH." -msgstr "" +msgstr "PATH 中没有找到 git" -#: git-gui.sh:662 +#: git-gui.sh:632 msgid "Cannot parse Git version string:" -msgstr "" +msgstr "无法解析 Git 的版本信息:" -#: git-gui.sh:680 +#: git-gui.sh:650 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -53,388 +70,386 @@ msgid "" "\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 +#: git-gui.sh:888 msgid "Git directory not found:" -msgstr "" +msgstr "Git 目录无法找到:" -#: git-gui.sh:860 +#: git-gui.sh:895 msgid "Cannot move to top of working directory:" -msgstr "" +msgstr "无法移动到工作根目录:" -#: git-gui.sh:867 +#: git-gui.sh:902 msgid "Cannot use funny .git directory:" -msgstr "" +msgstr "无法使用 .git 目录:" -#: git-gui.sh:872 +#: git-gui.sh:907 msgid "No working directory" -msgstr "" +msgstr "没有工作目录" -#: git-gui.sh:1019 +#: git-gui.sh:1054 msgid "Refreshing file status..." -msgstr "" +msgstr "更新文件状态..." -#: git-gui.sh:1084 +#: git-gui.sh:1119 msgid "Scanning for modified files ..." -msgstr "" +msgstr "扫描修改过的文件 ..." -#: git-gui.sh:1259 lib/browser.tcl:245 -#, fuzzy +#: git-gui.sh:1294 lib/browser.tcl:245 msgid "Ready." -msgstr "重做" +msgstr "就绪" -#: git-gui.sh:1525 +#: git-gui.sh:1560 msgid "Unmodified" -msgstr "" +msgstr "未修改" -#: git-gui.sh:1527 +#: git-gui.sh:1562 msgid "Modified, not staged" -msgstr "" +msgstr "修改但未缓存" -#: git-gui.sh:1528 git-gui.sh:1533 -#, fuzzy +#: git-gui.sh:1563 git-gui.sh:1568 msgid "Staged for commit" -msgstr "从本次提交移除" +msgstr "缓存为提交" -#: git-gui.sh:1529 git-gui.sh:1534 -#, fuzzy +#: git-gui.sh:1564 git-gui.sh:1569 msgid "Portions staged for commit" -msgstr "从本次提交移除" +msgstr "部分缓存为提交" -#: git-gui.sh:1530 git-gui.sh:1535 +#: git-gui.sh:1565 git-gui.sh:1570 msgid "Staged for commit, missing" -msgstr "" +msgstr "缓存为提交, 不存在" -#: git-gui.sh:1532 +#: git-gui.sh:1567 msgid "Untracked, not staged" -msgstr "" +msgstr "未跟踪, 未缓存" -#: git-gui.sh:1537 +#: git-gui.sh:1572 msgid "Missing" -msgstr "" +msgstr "不存在" -#: git-gui.sh:1538 +#: git-gui.sh:1573 msgid "Staged for removal" -msgstr "" +msgstr "缓存为删除" -#: git-gui.sh:1539 +#: git-gui.sh:1574 msgid "Staged for removal, still present" -msgstr "" +msgstr "缓存为删除, 但仍存在" -#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579 msgid "Requires merge resolution" -msgstr "" +msgstr "需要解决合并冲突" -#: git-gui.sh:1579 +#: git-gui.sh:1614 msgid "Starting gitk... please wait..." -msgstr "" +msgstr "启动 gitk... 请等待..." -#: git-gui.sh:1588 +#: git-gui.sh:1623 #, 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 +#: git-gui.sh:1823 lib/choose_repository.tcl:35 msgid "Repository" -msgstr "版本树" +msgstr "版本库(repository)" -#: git-gui.sh:1789 +#: git-gui.sh:1824 msgid "Edit" msgstr "编辑" -#: git-gui.sh:1791 lib/choose_rev.tcl:560 +#: git-gui.sh:1826 lib/choose_rev.tcl:560 msgid "Branch" -msgstr "分支" +msgstr "分支(branch)" -#: git-gui.sh:1794 lib/choose_rev.tcl:547 -#, fuzzy +#: git-gui.sh:1829 lib/choose_rev.tcl:547 msgid "Commit@@noun" -msgstr "提交" +msgstr "提交(commit)" -#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" -msgstr "合并" +msgstr "合并(merge)" -#: git-gui.sh:1798 lib/choose_rev.tcl:556 -#, fuzzy +#: git-gui.sh:1833 lib/choose_rev.tcl:556 msgid "Remote" -msgstr "改名..." +msgstr "远端(remote)" -#: git-gui.sh:1807 +#: git-gui.sh:1842 msgid "Browse Current Branch's Files" -msgstr "浏览当前分支文件" +msgstr "浏览当前分支上的文件" -#: git-gui.sh:1811 -#, fuzzy +#: git-gui.sh:1846 msgid "Browse Branch Files..." -msgstr "浏览当前分支文件" +msgstr "浏览分支上的文件..." -#: git-gui.sh:1816 +#: git-gui.sh:1851 msgid "Visualize Current Branch's History" -msgstr "调用gitk显示当前分支" +msgstr "图示当前分支的历史" -#: git-gui.sh:1820 +#: git-gui.sh:1855 msgid "Visualize All Branch History" -msgstr "调用gitk显示所有分支" +msgstr "图示所有分支的历史" -#: git-gui.sh:1827 -#, fuzzy, tcl-format +#: git-gui.sh:1862 +#, tcl-format msgid "Browse %s's Files" -msgstr "浏览当前分支文件" +msgstr "浏览 %s 上的文件" -#: git-gui.sh:1829 -#, fuzzy, tcl-format +#: git-gui.sh:1864 +#, tcl-format msgid "Visualize %s's History" -msgstr "调用gitk显示所有分支" +msgstr "图示 %s 分支的历史" -#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" -msgstr "数据库统计数据" +msgstr "数据库统计信息" -#: git-gui.sh:1837 lib/database.tcl:34 +#: git-gui.sh:1872 lib/database.tcl:34 msgid "Compress Database" msgstr "压缩数据库" -#: git-gui.sh:1840 +#: git-gui.sh:1875 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 +#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 msgid "Create Desktop Icon" msgstr "创建桌面图标" -#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184 msgid "Quit" msgstr "退出" -#: git-gui.sh:1867 +#: git-gui.sh:1902 msgid "Undo" msgstr "撤销" -#: git-gui.sh:1870 +#: git-gui.sh:1905 msgid "Redo" msgstr "重做" -#: git-gui.sh:1874 git-gui.sh:2366 +#: git-gui.sh:1909 git-gui.sh:2403 msgid "Cut" msgstr "剪切" -#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549 #: lib/console.tcl:67 msgid "Copy" msgstr "复制" -#: git-gui.sh:1880 git-gui.sh:2372 +#: git-gui.sh:1915 git-gui.sh:2409 msgid "Paste" msgstr "粘贴" -#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: git-gui.sh:1918 git-gui.sh:2412 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 +#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69 msgid "Select All" msgstr "全选" -#: git-gui.sh:1896 +#: git-gui.sh:1931 msgid "Create..." msgstr "新建..." -#: git-gui.sh:1902 +#: git-gui.sh:1937 msgid "Checkout..." -msgstr "切换..." +msgstr "Checkout..." -#: git-gui.sh:1908 +#: git-gui.sh:1943 msgid "Rename..." -msgstr "改名..." +msgstr "更名..." -#: git-gui.sh:1913 git-gui.sh:2012 +#: git-gui.sh:1948 git-gui.sh:2048 msgid "Delete..." msgstr "删除..." -#: git-gui.sh:1918 +#: git-gui.sh:1953 msgid "Reset..." -msgstr "重置所有修动..." +msgstr "复位(Reset)..." -#: git-gui.sh:1930 git-gui.sh:2313 +#: git-gui.sh:1965 git-gui.sh:2350 msgid "New Commit" -msgstr "新提交" +msgstr "新建提交" -#: git-gui.sh:1938 git-gui.sh:2320 +#: git-gui.sh:1973 git-gui.sh:2357 msgid "Amend Last Commit" -msgstr "修订上次提交" +msgstr "修正上次提交" -#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "重新扫描" -#: git-gui.sh:1953 -#, fuzzy +#: git-gui.sh:1988 msgid "Stage To Commit" -msgstr "从本次提交移除" +msgstr "缓存为提交" -#: git-gui.sh:1958 -#, fuzzy +#: git-gui.sh:1994 msgid "Stage Changed Files To Commit" -msgstr "将被提交的修改" +msgstr "缓存修改的文件为提交" -#: git-gui.sh:1964 +#: git-gui.sh:2000 msgid "Unstage From Commit" -msgstr "从本次提交移除" +msgstr "从本次提交撤除" -#: git-gui.sh:1969 lib/index.tcl:352 +#: git-gui.sh:2005 lib/index.tcl:393 msgid "Revert Changes" -msgstr "恢复修改" +msgstr "撤销修改" -#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427 msgid "Sign Off" -msgstr "签名" +msgstr "签名(Sign Off)" -#: git-gui.sh:1980 git-gui.sh:2296 -#, fuzzy +#: git-gui.sh:2016 git-gui.sh:2333 msgid "Commit@@verb" msgstr "提交" -#: git-gui.sh:1991 +#: git-gui.sh:2027 msgid "Local Merge..." msgstr "本地合并..." -#: git-gui.sh:1996 +#: git-gui.sh:2032 msgid "Abort Merge..." -msgstr "取消合并..." +msgstr "中止合并..." -#: git-gui.sh:2008 +#: git-gui.sh:2044 msgid "Push..." msgstr "上传..." -#: git-gui.sh:2019 lib/choose_repository.tcl:41 +#: git-gui.sh:2055 lib/choose_repository.tcl:40 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 +#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13 +#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49 #, tcl-format msgid "About %s" -msgstr "关于%s" +msgstr "关于 %s" -#: git-gui.sh:2026 +#: git-gui.sh:2062 msgid "Preferences..." -msgstr "" +msgstr "首选项..." -#: git-gui.sh:2034 git-gui.sh:2558 +#: git-gui.sh:2070 git-gui.sh:2595 msgid "Options..." msgstr "选项..." -#: git-gui.sh:2040 lib/choose_repository.tcl:47 +#: git-gui.sh:2076 lib/choose_repository.tcl:46 msgid "Help" msgstr "帮助" -#: git-gui.sh:2081 +#: git-gui.sh:2117 msgid "Online Documentation" msgstr "在线文档" -#: git-gui.sh:2165 +#: git-gui.sh:2201 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" -msgstr "" +msgstr "致命错误: 无法获取路径 %s 的信息: 该文件或目录不存在" -#: git-gui.sh:2198 +#: git-gui.sh:2234 msgid "Current Branch:" msgstr "当前分支:" -#: git-gui.sh:2219 -#, fuzzy +#: git-gui.sh:2255 msgid "Staged Changes (Will Commit)" -msgstr "将被提交的修改" +msgstr "已缓存的改动 (将被提交)" -#: git-gui.sh:2239 +#: git-gui.sh:2274 msgid "Unstaged Changes" -msgstr "" +msgstr "未缓存的改动" -#: git-gui.sh:2286 +#: git-gui.sh:2323 msgid "Stage Changed" -msgstr "" +msgstr "缓存改动" -#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182 msgid "Push" msgstr "上传" -#: git-gui.sh:2332 +#: git-gui.sh:2369 msgid "Initial Commit Message:" -msgstr "初始提交描述:" +msgstr "初始的提交描述:" -#: git-gui.sh:2333 +#: git-gui.sh:2370 msgid "Amended Commit Message:" -msgstr "修订提交描述:" +msgstr "修正的提交描述:" -#: git-gui.sh:2334 +#: git-gui.sh:2371 msgid "Amended Initial Commit Message:" -msgstr "修订初始提交描述:" +msgstr "修正的初始提交描述:" -#: git-gui.sh:2335 +#: git-gui.sh:2372 msgid "Amended Merge Commit Message:" -msgstr "修订合并提交描述:" +msgstr "修正的合并提交描述:" -#: git-gui.sh:2336 +#: git-gui.sh:2373 msgid "Merge Commit Message:" msgstr "合并提交描述:" -#: git-gui.sh:2337 +#: git-gui.sh:2374 msgid "Commit Message:" msgstr "提交描述:" -#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71 msgid "Copy All" msgstr "全部复制" -#: git-gui.sh:2406 lib/blame.tcl:104 +#: git-gui.sh:2443 lib/blame.tcl:104 msgid "File:" -msgstr "" +msgstr "文件:" -#: git-gui.sh:2508 +#: git-gui.sh:2545 msgid "Refresh" msgstr "刷新" -#: git-gui.sh:2529 +#: git-gui.sh:2566 msgid "Apply/Reverse Hunk" msgstr "应用/撤消此修改块" -#: git-gui.sh:2535 +#: git-gui.sh:2572 msgid "Decrease Font Size" msgstr "缩小字体" -#: git-gui.sh:2539 +#: git-gui.sh:2576 msgid "Increase Font Size" msgstr "放大字体" -#: git-gui.sh:2544 +#: git-gui.sh:2581 msgid "Show Less Context" -msgstr "显示更多diff上下文" +msgstr "显示更少上下文" -#: git-gui.sh:2551 +#: git-gui.sh:2588 msgid "Show More Context" -msgstr "显示更少diff上下文" +msgstr "显示更多上下文" -#: git-gui.sh:2565 -#, fuzzy +#: git-gui.sh:2602 msgid "Unstage Hunk From Commit" -msgstr "从本次提交移除" +msgstr "从提交中撤除修改块" -#: git-gui.sh:2567 -#, fuzzy +#: git-gui.sh:2604 msgid "Stage Hunk For Commit" -msgstr "从本次提交移除" +msgstr "缓存修改块为提交" -#: git-gui.sh:2586 +#: git-gui.sh:2623 msgid "Initializing..." -msgstr "" +msgstr "初始化..." -#: git-gui.sh:2677 +#: git-gui.sh:2718 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -444,15 +459,22 @@ msgid "" "by %s:\n" "\n" msgstr "" +"可能存在环境变量的问题.\n" +"\n" +"由 %s 执行的 Git 子进程可能忽略下列环境变量:\n" +"\n" -#: git-gui.sh:2707 +#: git-gui.sh:2748 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 +#: git-gui.sh:2753 #, tcl-format msgid "" "\n" @@ -462,206 +484,197 @@ msgid "" "user.email settings into your personal\n" "~/.gitconfig file.\n" msgstr "" +"\n" +"\n" +"%s 的一个很好的替代方案是将 user.name 以及\n" +"user.email 设置放在你的个人 ~/.gitconfig 文件中.\n" #: lib/about.tcl:25 msgid "git-gui - a graphical user interface for Git." -msgstr "" +msgstr "git-gui - Git 的图形化用户界面" #: lib/blame.tcl:77 msgid "File Viewer" -msgstr "" +msgstr "文件查看器" #: lib/blame.tcl:81 -#, fuzzy msgid "Commit:" -msgstr "提交" +msgstr "提交:" #: lib/blame.tcl:249 -#, fuzzy msgid "Copy Commit" -msgstr "提交" +msgstr "复制提交" #: lib/blame.tcl:369 #, tcl-format msgid "Reading %s..." -msgstr "" +msgstr "读取 %s..." #: lib/blame.tcl:473 msgid "Loading copy/move tracking annotations..." -msgstr "" +msgstr "装载复制/移动跟踪标注..." #: lib/blame.tcl:493 msgid "lines annotated" -msgstr "" +msgstr "标注行" #: lib/blame.tcl:674 msgid "Loading original location annotations..." -msgstr "" +msgstr "装载原始位置标注..." #: lib/blame.tcl:677 msgid "Annotation complete." -msgstr "" +msgstr "标注完成." #: lib/blame.tcl:731 msgid "Loading annotation..." -msgstr "" +msgstr "裝載标注..." #: lib/blame.tcl:787 msgid "Author:" -msgstr "" +msgstr "作者:" #: lib/blame.tcl:791 -#, fuzzy msgid "Committer:" -msgstr "提交" +msgstr "提交者:" #: lib/blame.tcl:796 msgid "Original File:" -msgstr "" +msgstr "原始文件:" #: lib/blame.tcl:910 msgid "Originally By:" -msgstr "" +msgstr "最初由:" #: lib/blame.tcl:916 msgid "In File:" -msgstr "" +msgstr "在文件:" #: lib/blame.tcl:921 msgid "Copied Or Moved Here By:" -msgstr "" +msgstr "由复制或移动至此:" #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 -#, fuzzy msgid "Checkout Branch" -msgstr "当前分支:" +msgstr "Checkout 分支" #: lib/branch_checkout.tcl:23 -#, fuzzy msgid "Checkout" -msgstr "切换..." +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 "" +msgstr "取消" #: lib/branch_checkout.tcl:32 lib/browser.tcl:286 msgid "Revision" -msgstr "" +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 "" +msgstr "获取跟踪分支" #: lib/branch_checkout.tcl:44 msgid "Detach From Local Branch" -msgstr "" +msgstr "从本地分支脱离" #: lib/branch_create.tcl:22 -#, fuzzy msgid "Create Branch" -msgstr "当前分支:" +msgstr "创建分支" #: lib/branch_create.tcl:27 -#, fuzzy msgid "Create New Branch" -msgstr "当前分支:" +msgstr "新建分支" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 -#, fuzzy +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375 msgid "Create" -msgstr "新建..." +msgstr "新建" #: lib/branch_create.tcl:40 -#, fuzzy msgid "Branch Name" -msgstr "分支" +msgstr "分支名" #: lib/branch_create.tcl:43 msgid "Name:" -msgstr "" +msgstr "名字:" #: lib/branch_create.tcl:58 msgid "Match Tracking Branch Name" -msgstr "" +msgstr "匹配跟踪分支名字" #: lib/branch_create.tcl:66 msgid "Starting Revision" -msgstr "" +msgstr "起始版本" #: lib/branch_create.tcl:72 msgid "Update Existing Branch:" -msgstr "" +msgstr "更新已有分支:" #: lib/branch_create.tcl:75 msgid "No" -msgstr "" +msgstr "号码" #: lib/branch_create.tcl:80 msgid "Fast Forward Only" -msgstr "" +msgstr "仅快速合并" #: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 -#, fuzzy msgid "Reset" -msgstr "重置所有修动..." +msgstr "复位" #: lib/branch_create.tcl:97 msgid "Checkout After Creation" -msgstr "" +msgstr "在创建后Checkout" #: lib/branch_create.tcl:131 msgid "Please select a tracking branch." -msgstr "" +msgstr "请选择某个跟踪分支." #: lib/branch_create.tcl:140 #, tcl-format msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "" +msgstr "跟踪分支 %s 并不是远端版本库中的一个分支" #: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 msgid "Please supply a branch name." -msgstr "" +msgstr "请提供分支名字." #: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 #, tcl-format msgid "'%s' is not an acceptable branch name." -msgstr "" +msgstr "'%s'不是一个可接受的分支名." #: lib/branch_delete.tcl:15 -#, fuzzy msgid "Delete Branch" -msgstr "当前分支:" +msgstr "删除分支" #: lib/branch_delete.tcl:20 msgid "Delete Local Branch" -msgstr "" +msgstr "删除本地分支" #: lib/branch_delete.tcl:37 -#, fuzzy msgid "Local Branches" -msgstr "分支" +msgstr "本地分支" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" -msgstr "" +msgstr "仅在合并后删除" #: lib/branch_delete.tcl:54 msgid "Always (Do not perform merge test.)" -msgstr "" +msgstr "总是合并 (不作合并测试.)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" -msgstr "" +msgstr "下列分支没有完全被合并到 %s:" #: lib/branch_delete.tcl:115 msgid "" @@ -669,6 +682,9 @@ msgid "" "\n" " Delete the selected branches?" msgstr "" +"恢复被删除的分支非常困难.\n" +"\n" +"是否要删除所选分支?" #: lib/branch_delete.tcl:141 #, tcl-format @@ -676,86 +692,84 @@ msgid "" "Failed to delete branches:\n" "%s" msgstr "" +"无法删除分支:\n" +"%s" #: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 -#, fuzzy msgid "Rename Branch" -msgstr "当前分支:" +msgstr "更改分支名:" #: lib/branch_rename.tcl:26 -#, fuzzy msgid "Rename" -msgstr "改名..." +msgstr "更名..." #: lib/branch_rename.tcl:36 -#, fuzzy msgid "Branch:" -msgstr "分支" +msgstr "分支:" #: lib/branch_rename.tcl:39 msgid "New Name:" -msgstr "" +msgstr "新名字:" #: lib/branch_rename.tcl:75 msgid "Please select a branch to rename." -msgstr "" +msgstr "请选择分支更名." #: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 #, tcl-format msgid "Branch '%s' already exists." -msgstr "" +msgstr "分支 '%s' 已经存在." #: lib/branch_rename.tcl:117 #, tcl-format msgid "Failed to rename '%s'." -msgstr "" +msgstr "无法更名 '%s'." #: lib/browser.tcl:17 msgid "Starting..." -msgstr "" +msgstr "开始..." #: lib/browser.tcl:26 msgid "File Browser" -msgstr "" +msgstr "文件浏览器" #: lib/browser.tcl:125 lib/browser.tcl:142 #, tcl-format msgid "Loading %s..." -msgstr "" +msgstr "装载 %s..." #: lib/browser.tcl:186 msgid "[Up To Parent]" -msgstr "" +msgstr "[上层目录]" #: lib/browser.tcl:266 lib/browser.tcl:272 -#, fuzzy msgid "Browse Branch Files" -msgstr "浏览当前分支文件" +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 +#: 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 "" +msgstr "浏览" #: lib/checkout_op.tcl:79 #, tcl-format msgid "Fetching %s from %s" -msgstr "" +msgstr "获取 %s 自 %s" #: lib/checkout_op.tcl:127 #, tcl-format msgid "fatal: Cannot resolve %s" -msgstr "" +msgstr "致命错误: 无法解决 %s" #: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 msgid "Close" -msgstr "" +msgstr "关闭" #: lib/checkout_op.tcl:169 #, tcl-format msgid "Branch '%s' does not exist." -msgstr "" +msgstr "分支 '%s' 并不存在." #: lib/checkout_op.tcl:206 #, tcl-format @@ -765,20 +779,24 @@ msgid "" "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 "" +msgstr "合并策略 '%s' 不支持." #: lib/checkout_op.tcl:239 #, tcl-format msgid "Failed to update '%s'." -msgstr "" +msgstr "无法更新 '%s'." #: lib/checkout_op.tcl:251 msgid "Staging area (index) is already locked." -msgstr "" +msgstr "缓存区域 (index) 已被锁定." #: lib/checkout_op.tcl:266 msgid "" @@ -789,25 +807,31 @@ msgid "" "\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 "" +msgstr "更新工作目录到 '%s'..." #: lib/checkout_op.tcl:353 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "" +msgstr "中止 '%s' 的 checkout 操作 (需要做文件级合并)." #: lib/checkout_op.tcl:354 msgid "File level merge required." -msgstr "" +msgstr "需要文件级合并." #: lib/checkout_op.tcl:358 #, tcl-format msgid "Staying on branch '%s'." -msgstr "" +msgstr "停留在分支 '%s'." #: lib/checkout_op.tcl:429 msgid "" @@ -816,29 +840,32 @@ msgid "" "If you wanted to be on a branch, create one now starting from 'This Detached " "Checkout'." msgstr "" +"你不在某个本地分支上.\n" +"\n" +"如果你想位于某分支上, 从当前脱节的Checkout中创建一个新分支." #: lib/checkout_op.tcl:446 -#, fuzzy, tcl-format +#, tcl-format msgid "Checked out '%s'." -msgstr "切换..." +msgstr "'%s' 已被 checkout" #: lib/checkout_op.tcl:478 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" -msgstr "" +msgstr "复位 '%s' 到 '%s' 将导致下列提交的丢失:" #: lib/checkout_op.tcl:500 msgid "Recovering lost commits may not be easy." -msgstr "" +msgstr "恢复丢失的提交是比较困难的." #: lib/checkout_op.tcl:505 #, tcl-format msgid "Reset '%s'?" -msgstr "" +msgstr "复位 '%s'?" #: lib/checkout_op.tcl:510 lib/merge.tcl:164 msgid "Visualize" -msgstr "" +msgstr "图示" #: lib/checkout_op.tcl:578 #, tcl-format @@ -850,286 +877,301 @@ msgid "" "\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 -#, fuzzy msgid "Select" -msgstr "全选" +msgstr "选择" #: lib/choose_font.tcl:53 msgid "Font Family" -msgstr "" +msgstr "字体族" #: lib/choose_font.tcl:73 -#, fuzzy msgid "Font Size" -msgstr "缩小字体" +msgstr "字体大小" #: lib/choose_font.tcl:90 msgid "Font Example" -msgstr "" +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 +#: lib/choose_repository.tcl:27 msgid "Git Gui" -msgstr "" +msgstr "Git Gui" -#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 -#, fuzzy +#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380 msgid "Create New Repository" -msgstr "版本树" +msgstr "创建新的版本库" -#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 -#, fuzzy +#: lib/choose_repository.tcl:86 +msgid "New..." +msgstr "新建..." + +#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468 msgid "Clone Existing Repository" -msgstr "版本树" +msgstr "克隆已有版本库" -#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 -#, fuzzy +#: lib/choose_repository.tcl:99 +msgid "Clone..." +msgstr "克隆..." + +#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978 msgid "Open Existing Repository" -msgstr "版本树" +msgstr "打开已有版本库" -#: lib/choose_repository.tcl:91 -msgid "Next >" -msgstr "" +#: lib/choose_repository.tcl:112 +msgid "Open..." +msgstr "打开..." -#: lib/choose_repository.tcl:152 +#: 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 "" +msgstr "位置 %s 已经存在." -#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 -#: lib/choose_repository.tcl:172 +#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307 +#: lib/choose_repository.tcl:314 #, tcl-format msgid "Failed to create repository %s:" -msgstr "" +msgstr "无法创建版本库 %s:" -#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486 msgid "Directory:" -msgstr "" +msgstr "目录:" -#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 -#: lib/choose_repository.tcl:834 -#, fuzzy +#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544 +#: lib/choose_repository.tcl:1013 msgid "Git Repository" -msgstr "版本树" +msgstr "Git 版本库" -#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437 #, tcl-format msgid "Directory %s already exists." -msgstr "" +msgstr "目录 %s 已经存在." -#: lib/choose_repository.tcl:265 +#: lib/choose_repository.tcl:442 #, tcl-format msgid "File %s already exists." -msgstr "" +msgstr "文件 %s 已经存在." -#: lib/choose_repository.tcl:286 +#: lib/choose_repository.tcl:463 msgid "Clone" -msgstr "" +msgstr "克隆" -#: lib/choose_repository.tcl:299 +#: lib/choose_repository.tcl:476 msgid "URL:" -msgstr "" +msgstr "URL:" -#: lib/choose_repository.tcl:319 +#: lib/choose_repository.tcl:496 msgid "Clone Type:" -msgstr "" +msgstr "克隆类型:" -#: lib/choose_repository.tcl:325 +#: lib/choose_repository.tcl:502 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" -msgstr "" +msgstr "标准方式 (快速, 部分备份, 作硬连接)" -#: lib/choose_repository.tcl:331 +#: lib/choose_repository.tcl:508 msgid "Full Copy (Slower, Redundant Backup)" -msgstr "" +msgstr "全部复制 (较慢, 做备份)" -#: lib/choose_repository.tcl:337 +#: lib/choose_repository.tcl:514 msgid "Shared (Fastest, Not Recommended, No Backup)" -msgstr "" +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 +#: 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 "" +msgstr "不是一个 Git 版本库: %s" -#: lib/choose_repository.tcl:405 +#: lib/choose_repository.tcl:586 msgid "Standard only available for local repository." -msgstr "" +msgstr "标准方式仅当是本地版本库时有效." -#: lib/choose_repository.tcl:409 +#: lib/choose_repository.tcl:590 msgid "Shared only available for local repository." -msgstr "" +msgstr "共享方式仅当是本地版本库时有效." -#: lib/choose_repository.tcl:439 +#: lib/choose_repository.tcl:617 msgid "Failed to configure origin" -msgstr "" +msgstr "无法配置 origin" -#: lib/choose_repository.tcl:451 +#: lib/choose_repository.tcl:629 msgid "Counting objects" -msgstr "" +msgstr "清点对象" -#: lib/choose_repository.tcl:452 +#: lib/choose_repository.tcl:630 +#, fuzzy msgid "buckets" -msgstr "" +msgstr "水桶??" -#: lib/choose_repository.tcl:476 +#: lib/choose_repository.tcl:654 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" -msgstr "" +msgstr "无法复制 objects/info/alternates: %s" -#: lib/choose_repository.tcl:512 +#: lib/choose_repository.tcl:690 #, tcl-format msgid "Nothing to clone from %s." -msgstr "" +msgstr "没有东西可从 %s 克隆." -#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 -#: lib/choose_repository.tcl:740 +#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906 +#: lib/choose_repository.tcl:918 msgid "The 'master' branch has not been initialized." -msgstr "" +msgstr "'master'分支尚未初始化." -#: lib/choose_repository.tcl:527 +#: lib/choose_repository.tcl:705 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "" +msgstr "硬连接不可用. 使用复制." -#: lib/choose_repository.tcl:539 +#: lib/choose_repository.tcl:717 #, tcl-format msgid "Cloning from %s" -msgstr "" +msgstr "从 %s 克隆" -#: lib/choose_repository.tcl:570 -#, fuzzy +#: lib/choose_repository.tcl:748 msgid "Copying objects" -msgstr "压缩数据库" +msgstr "复制 objects" -#: lib/choose_repository.tcl:571 +#: lib/choose_repository.tcl:749 msgid "KiB" -msgstr "" +msgstr "KiB" -#: lib/choose_repository.tcl:595 +#: lib/choose_repository.tcl:773 #, tcl-format msgid "Unable to copy object: %s" -msgstr "" +msgstr "无法复制 object: %s" -#: lib/choose_repository.tcl:605 +#: lib/choose_repository.tcl:783 msgid "Linking objects" -msgstr "" +msgstr "链接 objects" -#: lib/choose_repository.tcl:606 +#: lib/choose_repository.tcl:784 msgid "objects" -msgstr "" +msgstr "objects" -#: lib/choose_repository.tcl:614 +#: lib/choose_repository.tcl:792 #, tcl-format msgid "Unable to hardlink object: %s" -msgstr "" +msgstr "无法硬链接 object: %s" -#: lib/choose_repository.tcl:669 +#: lib/choose_repository.tcl:847 msgid "Cannot fetch branches and objects. See console output for details." -msgstr "" +msgstr "无法获取分支和对象. 请查看控制终端的输出." -#: lib/choose_repository.tcl:680 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch tags. See console output for details." -msgstr "" +msgstr "无法获取标签. 请查看控制终端的输出." -#: lib/choose_repository.tcl:704 +#: lib/choose_repository.tcl:882 msgid "Cannot determine HEAD. See console output for details." -msgstr "" +msgstr "无法确定 HEAD. 请查看控制终端的输出." -#: lib/choose_repository.tcl:713 +#: lib/choose_repository.tcl:891 #, tcl-format msgid "Unable to cleanup %s" -msgstr "" +msgstr "无法清理 %s" -#: lib/choose_repository.tcl:719 +#: lib/choose_repository.tcl:897 msgid "Clone failed." -msgstr "" +msgstr "克隆失败." -#: lib/choose_repository.tcl:726 +#: lib/choose_repository.tcl:904 msgid "No default branch obtained." -msgstr "" +msgstr "没有获取缺省分支" -#: lib/choose_repository.tcl:737 +#: lib/choose_repository.tcl:915 #, tcl-format msgid "Cannot resolve %s as a commit." -msgstr "" +msgstr "无法解析 %s 为提交." -#: lib/choose_repository.tcl:749 +#: lib/choose_repository.tcl:927 msgid "Creating working directory" -msgstr "" +msgstr "创建工作目录" -#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 -#: lib/index.tcl:149 +#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127 +#: lib/index.tcl:193 msgid "files" -msgstr "" +msgstr "文件" -#: lib/choose_repository.tcl:779 +#: lib/choose_repository.tcl:957 msgid "Initial file checkout failed." -msgstr "" +msgstr "初始的文件checkout失败" -#: lib/choose_repository.tcl:795 +#: lib/choose_repository.tcl:973 msgid "Open" -msgstr "" +msgstr "打开" -#: lib/choose_repository.tcl:805 -#, fuzzy +#: lib/choose_repository.tcl:983 msgid "Repository:" -msgstr "版本树" +msgstr "版本库" -#: lib/choose_repository.tcl:854 +#: lib/choose_repository.tcl:1033 #, tcl-format msgid "Failed to open repository %s:" -msgstr "" +msgstr "无法打开版本库 %s:" #: lib/choose_rev.tcl:53 msgid "This Detached Checkout" -msgstr "" +msgstr "该脱节的Checkout" #: lib/choose_rev.tcl:60 msgid "Revision Expression:" -msgstr "" +msgstr "版本表达式:" #: lib/choose_rev.tcl:74 -#, fuzzy msgid "Local Branch" -msgstr "分支" +msgstr "本地分支" #: lib/choose_rev.tcl:79 -#, fuzzy msgid "Tracking Branch" -msgstr "当前分支:" +msgstr "跟踪分支:" #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 msgid "Tag" -msgstr "" +msgstr "标签" #: lib/choose_rev.tcl:317 #, tcl-format msgid "Invalid revision: %s" -msgstr "" +msgstr "无效版本: %s" #: lib/choose_rev.tcl:338 msgid "No revision selected." -msgstr "" +msgstr "没有选择版本." #: lib/choose_rev.tcl:346 msgid "Revision expression is empty." -msgstr "" +msgstr "版本表达式为空." #: lib/choose_rev.tcl:530 msgid "Updated" -msgstr "" +msgstr "已更新" #: lib/choose_rev.tcl:558 msgid "URL" -msgstr "" +msgstr "URL" #: lib/commit.tcl:9 msgid "" @@ -1138,6 +1180,9 @@ msgid "" "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 "" @@ -1147,18 +1192,22 @@ msgid "" "completed. You cannot amend the prior commit unless you first abort the " "current merge activity.\n" msgstr "" +"在合并时无法修正.\n" +"\n" +"你当前正在一次尚未完成的合并操作过程中. 除非中止当前合并活动,\n" +"否则无法修正之前的提交.\n" #: lib/commit.tcl:49 msgid "Error loading commit data for amend:" -msgstr "" +msgstr "为修正装载提交数据出错:" #: lib/commit.tcl:76 msgid "Unable to obtain your identity:" -msgstr "" +msgstr "无法获知你的身份:" #: lib/commit.tcl:81 msgid "Invalid GIT_COMMITTER_IDENT:" -msgstr "" +msgstr "无效的 GIT_COMMITTER_IDENT" #: lib/commit.tcl:133 msgid "" @@ -1169,6 +1218,12 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" +"最后一次扫描的状态和当前版本库状态不符.\n" +"\n" +"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫" +"描.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/commit.tcl:154 #, tcl-format @@ -1178,6 +1233,9 @@ msgid "" "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 @@ -1186,6 +1244,9 @@ msgid "" "\n" "File %s cannot be committed by this program.\n" msgstr "" +"检测到未知文件状态 %s.\n" +"\n" +"文件 %s 无法由该程序提交.\n" #: lib/commit.tcl:170 msgid "" @@ -1193,6 +1254,9 @@ msgid "" "\n" "You must stage at least 1 file before you can commit.\n" msgstr "" +"没有需要提交的变动.\n" +"\n" +"提交前你必须首先缓存至少一个文件.\n" #: lib/commit.tcl:183 msgid "" @@ -1200,19 +1264,26 @@ msgid "" "\n" "A good commit message has the following format:\n" "\n" -"- First line: Describe in one sentance what you did.\n" +"- First line: Describe in one sentence 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 "" +msgstr "write-tree 失败:" #: lib/commit.tcl:275 #, tcl-format msgid "Commit %s appears to be corrupt" -msgstr "" +msgstr "提交 %s 似乎已损坏" #: lib/commit.tcl:279 msgid "" @@ -1222,77 +1293,81 @@ msgid "" "\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 "" +msgstr "没有改动要提交." #: lib/commit.tcl:303 #, tcl-format msgid "warning: Tcl does not support encoding '%s'." -msgstr "" +msgstr "警告: Tcl 不支持编码方式 '%s'." #: lib/commit.tcl:317 msgid "commit-tree failed:" -msgstr "" +msgstr "commit-tree 失败:" #: lib/commit.tcl:339 msgid "update-ref failed:" -msgstr "" +msgstr "update-ref 失败:" #: lib/commit.tcl:430 #, tcl-format msgid "Created commit %s: %s" -msgstr "" +msgstr "创建了 commit %s: %s" #: lib/console.tcl:57 msgid "Working... please wait..." -msgstr "" +msgstr "工作中... 请等待..." #: lib/console.tcl:183 msgid "Success" -msgstr "" +msgstr "成功" #: lib/console.tcl:196 msgid "Error: Command Failed" -msgstr "" +msgstr "错误: 命令失败" #: lib/database.tcl:43 msgid "Number of loose objects" -msgstr "" +msgstr "松散对象的数量" #: lib/database.tcl:44 msgid "Disk space used by loose objects" -msgstr "" +msgstr "松散对象所使用的磁盘空间" #: lib/database.tcl:45 msgid "Number of packed objects" -msgstr "" +msgstr "压缩对象数量" #: lib/database.tcl:46 msgid "Number of packs" -msgstr "" +msgstr "压缩包数量" #: lib/database.tcl:47 msgid "Disk space used by packed objects" -msgstr "" +msgstr "压缩对象所使用的磁盘空间" #: lib/database.tcl:48 msgid "Packed objects waiting for pruning" -msgstr "" +msgstr "压缩对象等待清理" #: lib/database.tcl:49 msgid "Garbage files" -msgstr "" +msgstr "垃圾文件" #: lib/database.tcl:72 -#, fuzzy msgid "Compressing the object database" -msgstr "压缩数据库" +msgstr "压缩对象数据库" #: lib/database.tcl:83 msgid "Verifying the object database with fsck-objects" -msgstr "" +msgstr "使用 fsck-objects 验证对象数据库" #: lib/database.tcl:108 #, tcl-format @@ -1304,11 +1379,16 @@ msgid "" "\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 "" +msgstr "无效的日期: %s" #: lib/diff.tcl:42 #, tcl-format @@ -1323,80 +1403,107 @@ msgid "" "A rescan will be automatically started to find other files which may have " "the same state." msgstr "" +"未检测到改动.\n" +"\n" +"该文件的修改日期被另一个程序所更新, 但其内容并没有变化.\n" +"\n" +"对于类似情况的其他文件的重新扫描将自动开始." #: lib/diff.tcl:81 -#, tcl-format +#, fuzzy, tcl-format msgid "Loading diff of %s..." -msgstr "" +msgstr "装载 %s 的 diff ..." #: lib/diff.tcl:114 lib/diff.tcl:184 #, tcl-format msgid "Unable to display %s" -msgstr "" +msgstr "无法显示 %s" #: lib/diff.tcl:115 msgid "Error loading file:" -msgstr "" +msgstr "装载文件出错:" #: lib/diff.tcl:122 msgid "Git Repository (subproject)" -msgstr "" +msgstr "Git 版本库 (子项目)" #: lib/diff.tcl:134 msgid "* Binary file (not showing content)." -msgstr "" +msgstr "* 二进制文件 (不显示内容)." #: lib/diff.tcl:185 msgid "Error loading diff:" -msgstr "" +msgstr "装载 diff 错误:" #: lib/diff.tcl:302 msgid "Failed to unstage selected hunk." -msgstr "" +msgstr "无法将选择的代码段从缓存中删除." #: lib/diff.tcl:309 msgid "Failed to stage selected hunk." -msgstr "" +msgstr "无法缓存所选代码段." #: lib/error.tcl:12 lib/error.tcl:102 msgid "error" -msgstr "" +msgstr "错误" #: lib/error.tcl:28 msgid "warning" -msgstr "" +msgstr "警告" #: lib/error.tcl:81 msgid "You must correct the above errors before committing." -msgstr "" +msgstr "你必须在提交前修正上述错误." -#: lib/index.tcl:241 -#, fuzzy, tcl-format +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "无法解锁缓存 (index)" + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "缓存(Index)错误" + +#: lib/index.tcl:21 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "更新 Git 缓存(Index)失败, 重新扫描将自动开始以重新同步 git-gui." + +#: lib/index.tcl:27 +msgid "Continue" +msgstr "继续" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "解锁 Index" + +#: lib/index.tcl:282 +#, tcl-format msgid "Unstaging %s from commit" -msgstr "从本次提交移除" +msgstr "从提交缓存中删除 %s" -#: lib/index.tcl:285 +#: lib/index.tcl:326 #, tcl-format msgid "Adding %s" -msgstr "" +msgstr "添加 %s" -#: lib/index.tcl:340 -#, fuzzy, tcl-format +#: lib/index.tcl:381 +#, tcl-format msgid "Revert changes in file %s?" -msgstr "恢复修改" +msgstr "撤销文件 %s 中的改动?" -#: lib/index.tcl:342 +#: lib/index.tcl:383 #, tcl-format msgid "Revert changes in these %i files?" -msgstr "" +msgstr "撤销这些 (%i个) 文件的改动?" -#: lib/index.tcl:348 +#: lib/index.tcl:389 msgid "Any unstaged changes will be permanently lost by the revert." -msgstr "" +msgstr "任何未缓存的改动将在这次撤销中永久丢失." -#: lib/index.tcl:351 +#: lib/index.tcl:392 msgid "Do Nothing" -msgstr "" +msgstr "不做操作" #: lib/merge.tcl:13 msgid "" @@ -1404,6 +1511,9 @@ msgid "" "\n" "You must finish amending this commit before starting any type of merge.\n" msgstr "" +"修正时无法做合并.\n" +"\n" +"你必须完成对该提交的修正才能继续任何类型的合并操作.\n" #: lib/merge.tcl:27 msgid "" @@ -1414,6 +1524,12 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" +"最后一次扫描的状态和当前版本库状态不符.\n" +"\n" +"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫" +"描.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/merge.tcl:44 #, tcl-format @@ -1425,6 +1541,12 @@ msgid "" "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 @@ -1436,6 +1558,12 @@ msgid "" "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 @@ -1445,24 +1573,24 @@ msgstr "" #: lib/merge.tcl:119 #, tcl-format msgid "Merging %s and %s" -msgstr "" +msgstr "合并 %s 和 %s" #: lib/merge.tcl:131 msgid "Merge completed successfully." -msgstr "" +msgstr "合并成功完成." #: lib/merge.tcl:133 msgid "Merge failed. Conflict resolution is required." -msgstr "" +msgstr "合并失败. 需要解决冲突." #: lib/merge.tcl:158 #, tcl-format msgid "Merge Into %s" -msgstr "" +msgstr "合并到 %s" #: lib/merge.tcl:177 msgid "Revision To Merge" -msgstr "" +msgstr "要合并的版本" #: lib/merge.tcl:212 msgid "" @@ -1470,6 +1598,9 @@ msgid "" "\n" "You must finish amending this commit.\n" msgstr "" +"修正操作中无法中止.\n" +"\n" +"你必须先完成本次修正操作.\n" #: lib/merge.tcl:222 msgid "" @@ -1479,6 +1610,11 @@ msgid "" "\n" "Continue with aborting the current merge?" msgstr "" +"中止合并?\n" +"\n" +"中止当前的合并操作将导致 *所有* 尚未提交的改动丢失.\n" +"\n" +"是否要继续中止当前的合并操作?" #: lib/merge.tcl:228 msgid "" @@ -1488,150 +1624,137 @@ msgid "" "\n" "Continue with resetting the current changes?" msgstr "" +"是否复位当前改动?\n" +"\n" +"复位当前的改动将导致 *所有* 未提交的改动丢失.\n" +"\n" +"是否要继续复位当前的改动?" #: lib/merge.tcl:239 msgid "Aborting" -msgstr "" +msgstr "中止" #: lib/merge.tcl:266 msgid "Abort failed." -msgstr "" +msgstr "中止失败" #: lib/merge.tcl:268 msgid "Abort completed. Ready." -msgstr "" +msgstr "中止完成. 就绪." #: lib/option.tcl:82 msgid "Restore Defaults" -msgstr "" +msgstr "恢复默认值" #: lib/option.tcl:86 msgid "Save" -msgstr "" +msgstr "保存" #: lib/option.tcl:96 -#, fuzzy, tcl-format +#, tcl-format msgid "%s Repository" -msgstr "版本树" +msgstr "%s 版本库" #: lib/option.tcl:97 msgid "Global (All Repositories)" -msgstr "" +msgstr "全局 (所有版本库)" #: lib/option.tcl:103 msgid "User Name" -msgstr "" +msgstr "用户名" #: lib/option.tcl:104 msgid "Email Address" -msgstr "" +msgstr "Email 地址" #: lib/option.tcl:106 -#, fuzzy msgid "Summarize Merge Commits" -msgstr "修订合并提交描述:" +msgstr "概述合并提交:" #: lib/option.tcl:107 msgid "Merge Verbosity" -msgstr "" +msgstr "合并冗余度" #: lib/option.tcl:108 msgid "Show Diffstat After Merge" -msgstr "" +msgstr "在合并后显示 Diffstat" #: lib/option.tcl:110 msgid "Trust File Modification Timestamps" -msgstr "" +msgstr "相信文件的改动时间" #: lib/option.tcl:111 msgid "Prune Tracking Branches During Fetch" -msgstr "" +msgstr "获取时清除跟踪分支" #: lib/option.tcl:112 msgid "Match Tracking Branches" -msgstr "" +msgstr "匹配跟踪分支" #: lib/option.tcl:113 msgid "Number of Diff Context Lines" -msgstr "" +msgstr "Diff 上下文行数" #: lib/option.tcl:114 msgid "New Branch Name Template" -msgstr "" +msgstr "新建分支命名模板" #: lib/option.tcl:176 msgid "Change Font" -msgstr "" +msgstr "更改字体" #: lib/option.tcl:180 #, tcl-format msgid "Choose %s" -msgstr "" +msgstr "选择 %s" #: lib/option.tcl:186 msgid "pt." -msgstr "" +msgstr "磅" #: lib/option.tcl:200 msgid "Preferences" -msgstr "" +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 "上传" +msgstr "无法完全保存选项:" #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 msgid "Delete Remote Branch" -msgstr "" +msgstr "删除远端分支" #: lib/remote_branch_delete.tcl:47 -#, fuzzy msgid "From Repository" -msgstr "版本树" +msgstr "从版本库" #: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 msgid "Remote:" -msgstr "" +msgstr "Remote:" #: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 msgid "Arbitrary URL:" -msgstr "" +msgstr "任意 URL:" #: lib/remote_branch_delete.tcl:84 -#, fuzzy msgid "Branches" msgstr "分支" #: lib/remote_branch_delete.tcl:109 -#, fuzzy msgid "Delete Only If" -msgstr "删除" +msgstr "删除仅当" #: lib/remote_branch_delete.tcl:111 msgid "Merged Into:" -msgstr "" +msgstr "合并到" #: lib/remote_branch_delete.tcl:119 msgid "Always (Do not perform merge checks)" -msgstr "" +msgstr "总是合并 (不作合并检查)" #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." -msgstr "" +msgstr "'合并到' 需要指定某个分支" #: lib/remote_branch_delete.tcl:184 #, tcl-format @@ -1640,6 +1763,9 @@ msgid "" "\n" " - %s" msgstr "" +"下列分支没有被全部合并到 %s 中:\n" +"\n" +" - %s" #: lib/remote_branch_delete.tcl:189 #, tcl-format @@ -1647,10 +1773,11 @@ 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 "" +msgstr "请选择某个或多个分支来删除" #: lib/remote_branch_delete.tcl:216 msgid "" @@ -1658,112 +1785,108 @@ msgid "" "\n" "Delete the selected branches?" msgstr "" +"恢复被删除的分支非常困难.\n" +"\n" +"是否要删除所选分支?" #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" -msgstr "" +msgstr "从 %s 中删除分支" #: lib/remote_branch_delete.tcl:286 msgid "No repository selected." -msgstr "" +msgstr "没有选择版本库" #: lib/remote_branch_delete.tcl:291 #, tcl-format msgid "Scanning %s..." -msgstr "" +msgstr "正在扫描 %s..." -#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 -msgid "Cannot write script:" -msgstr "" +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "从..清除(prune)" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "从..获取(fetch)" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "上传到(push)" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "无法修改快捷方式:" -#: lib/shortcut.tcl:149 +#: lib/shortcut.tcl:136 msgid "Cannot write icon:" -msgstr "" +msgstr "无法修改图标:" #: lib/status_bar.tcl:83 #, tcl-format msgid "%s ... %*i of %*i %s (%3i%%)" -msgstr "" +msgstr "%s ... %*i of %*i %s (%3i%%)" #: lib/transport.tcl:6 -#, fuzzy, tcl-format +#, tcl-format msgid "fetch %s" -msgstr "导入" +msgstr "获取(fetch)" #: lib/transport.tcl:7 #, tcl-format msgid "Fetching new changes from %s" -msgstr "" +msgstr "从 %s 处获取新的改动" #: lib/transport.tcl:18 #, tcl-format msgid "remote prune %s" -msgstr "" +msgstr "清除远端 %s" #: lib/transport.tcl:19 #, tcl-format msgid "Pruning tracking branches deleted from %s" -msgstr "" +msgstr "清除" #: lib/transport.tcl:25 lib/transport.tcl:71 #, tcl-format msgid "push %s" -msgstr "" +msgstr "上传 %s" #: lib/transport.tcl:26 #, tcl-format msgid "Pushing changes to %s" -msgstr "" +msgstr "上传改动到 %s" #: lib/transport.tcl:72 #, tcl-format msgid "Pushing %s %s to %s" -msgstr "" +msgstr "上传 %s %s 到 %s" #: lib/transport.tcl:89 -#, fuzzy msgid "Push Branches" -msgstr "分支" +msgstr "上传分支" #: lib/transport.tcl:103 -#, fuzzy msgid "Source Branches" -msgstr "当前分支:" +msgstr "源端分支:" #: lib/transport.tcl:120 -#, fuzzy msgid "Destination Repository" -msgstr "版本树" +msgstr "目标版本库" #: lib/transport.tcl:158 msgid "Transfer Options" -msgstr "" +msgstr "传输选项" #: lib/transport.tcl:160 msgid "Force overwrite existing branch (may discard changes)" -msgstr "" +msgstr "强制覆盖已有的分支 (可能会丢失改动)" #: lib/transport.tcl:164 msgid "Use thin pack (for slow network connections)" -msgstr "" +msgstr "使用 thin pack (适用于低速网络连接)" #: 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 "上传..." +msgstr "包含标签" diff --git a/git-merge.sh b/git-merge.sh index 1c123a37e6..7dbbb1d79d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -37,6 +37,7 @@ use_strategies= allow_fast_forward=t allow_trivial_merge=t +squash= no_commit= dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ @@ -70,7 +71,7 @@ finish_up_to_date () { squash_message () { echo Squashed commit of the following: echo - git log --no-merges ^"$head" $remoteheads + git log --no-merges --pretty=medium ^"$head" $remoteheads } finish () { @@ -152,17 +153,21 @@ parse_config () { --summary) show_diffstat=t ;; --squash) - allow_fast_forward=t squash=t no_commit=t ;; + test "$allow_fast_forward" = t || + die "You cannot combine --squash with --no-ff." + squash=t no_commit=t ;; --no-squash) - allow_fast_forward=t squash= no_commit= ;; + squash= no_commit= ;; --commit) - allow_fast_forward=t squash= no_commit= ;; + no_commit= ;; --no-commit) - allow_fast_forward=t squash= no_commit=t ;; + no_commit=t ;; --ff) - allow_fast_forward=t squash= no_commit= ;; + allow_fast_forward=t ;; --no-ff) - allow_fast_forward=false squash= no_commit= ;; + test "$squash" != t || + die "You cannot combine --squash with --no-ff." + allow_fast_forward=f ;; -s|--strategy) shift case " $all_strategies " in diff --git a/git-mergetool.sh b/git-mergetool.sh index cbbb707959..5c86f69229 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -34,7 +34,7 @@ base_present () { cleanup_temp_files () { if test "$1" = --save-backup ; then - mv -- "$BACKUP" "$path.orig" + mv -- "$BACKUP" "$MERGED.orig" rm -f -- "$LOCAL" "$REMOTE" "$BASE" else rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP" @@ -67,14 +67,14 @@ resolve_symlink_merge () { read ans case "$ans" in [lL]*) - git checkout-index -f --stage=2 -- "$path" - git add -- "$path" + git checkout-index -f --stage=2 -- "$MERGED" + git add -- "$MERGED" cleanup_temp_files --save-backup return ;; [rR]*) - git checkout-index -f --stage=3 -- "$path" - git add -- "$path" + git checkout-index -f --stage=3 -- "$MERGED" + git add -- "$MERGED" cleanup_temp_files --save-backup return ;; @@ -95,12 +95,12 @@ resolve_deleted_merge () { read ans case "$ans" in [mMcC]*) - git add -- "$path" + git add -- "$MERGED" cleanup_temp_files --save-backup return ;; [dD]*) - git rm -- "$path" > /dev/null + git rm -- "$MERGED" > /dev/null cleanup_temp_files return ;; @@ -112,11 +112,11 @@ resolve_deleted_merge () { } check_unchanged () { - if test "$path" -nt "$BACKUP" ; then + if test "$MERGED" -nt "$BACKUP" ; then status=0; else while true; do - echo "$path seems unchanged." + echo "$MERGED seems unchanged." printf "Was the merge successful? [y/n] " read answer < /dev/tty case "$answer" in @@ -127,50 +127,38 @@ check_unchanged () { fi } -save_backup () { - if test "$status" -eq 0; then - mv -- "$BACKUP" "$path.orig" - fi -} - -remove_backup () { - if test "$status" -eq 0; then - rm "$BACKUP" - fi -} - merge_file () { - path="$1" + MERGED="$1" - f=`git ls-files -u -- "$path"` + f=`git ls-files -u -- "$MERGED"` if test -z "$f" ; then - if test ! -f "$path" ; then - echo "$path: file not found" + if test ! -f "$MERGED" ; then + echo "$MERGED: file not found" else - echo "$path: file does not need merging" + echo "$MERGED: file does not need merging" fi exit 1 fi - ext="$$$(expr "$path" : '.*\(\.[^/]*\)$')" - BACKUP="$path.BACKUP.$ext" - LOCAL="$path.LOCAL.$ext" - REMOTE="$path.REMOTE.$ext" - BASE="$path.BASE.$ext" + ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" + BACKUP="$MERGED.BACKUP.$ext" + LOCAL="$MERGED.LOCAL.$ext" + REMOTE="$MERGED.REMOTE.$ext" + BASE="$MERGED.BASE.$ext" - mv -- "$path" "$BACKUP" - cp -- "$BACKUP" "$path" + mv -- "$MERGED" "$BACKUP" + cp -- "$BACKUP" "$MERGED" - base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'` - local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'` - remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'` + base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'` + local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'` + remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'` - base_present && git cat-file blob ":1:$prefix$path" >"$BASE" 2>/dev/null - local_present && git cat-file blob ":2:$prefix$path" >"$LOCAL" 2>/dev/null - remote_present && git cat-file blob ":3:$prefix$path" >"$REMOTE" 2>/dev/null + base_present && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null + local_present && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null + remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null if test -z "$local_mode" -o -z "$remote_mode"; then - echo "Deleted merge conflict for '$path':" + echo "Deleted merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" resolve_deleted_merge @@ -178,14 +166,14 @@ merge_file () { fi if is_symlink "$local_mode" || is_symlink "$remote_mode"; then - echo "Symbolic link merge conflict for '$path':" + echo "Symbolic link merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" resolve_symlink_merge return fi - echo "Normal merge conflict for '$path':" + echo "Normal merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" printf "Hit return to start merge resolution tool (%s): " "$merge_tool" @@ -194,36 +182,32 @@ merge_file () { case "$merge_tool" in kdiff3) if base_present ; then - ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ - -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) + ("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \ + -o "$MERGED" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) else - ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ - -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) + ("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \ + -o "$MERGED" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) fi status=$? - remove_backup ;; tkdiff) if base_present ; then - "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -a "$BASE" -o "$MERGED" -- "$LOCAL" "$REMOTE" else - "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -o "$MERGED" -- "$LOCAL" "$REMOTE" fi status=$? - save_backup ;; meld|vimdiff) touch "$BACKUP" - "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -- "$LOCAL" "$MERGED" "$REMOTE" check_unchanged - save_backup ;; gvimdiff) - touch "$BACKUP" - "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE" - check_unchanged - save_backup - ;; + touch "$BACKUP" + "$merge_tool_path" -f -- "$LOCAL" "$MERGED" "$REMOTE" + check_unchanged + ;; xxdiff) touch "$BACKUP" if base_present ; then @@ -231,53 +215,68 @@ merge_file () { -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ - --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE" + --merged-file "$MERGED" -- "$LOCAL" "$BASE" "$REMOTE" else "$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" "$REMOTE" + --merged-file "$MERGED" -- "$LOCAL" "$REMOTE" fi check_unchanged - save_backup ;; opendiff) touch "$BACKUP" if base_present; then - "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat else - "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat fi check_unchanged - save_backup ;; ecmerge) touch "$BACKUP" if base_present; then - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path" + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$MERGED" else - "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path" + "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$MERGED" fi check_unchanged - save_backup ;; emerge) if base_present ; then - "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")" else - "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")" fi status=$? - save_backup + ;; + *) + if test -n "$merge_tool_cmd"; then + if test "$merge_tool_trust_exit_code" = "false"; then + touch "$BACKUP" + ( eval $merge_tool_cmd ) + check_unchanged + else + ( eval $merge_tool_cmd ) + status=$? + fi + fi ;; esac if test "$status" -ne 0; then - echo "merge of $path failed" 1>&2 - mv -- "$BACKUP" "$path" + echo "merge of $MERGED failed" 1>&2 + mv -- "$BACKUP" "$MERGED" exit 1 fi - git add -- "$path" + + if test "$merge_keep_backup" = "true"; then + mv -- "$BACKUP" "$MERGED.orig" + else + rm -- "$BACKUP" + fi + + git add -- "$MERGED" cleanup_temp_files } @@ -309,12 +308,20 @@ do shift done +valid_custom_tool() +{ + merge_tool_cmd="$(git config mergetool.$1.cmd)" + test -n "$merge_tool_cmd" +} + valid_tool() { case "$1" in kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) ;; # happy *) - return 1 + if ! valid_custom_tool "$1"; then + return 1 + fi ;; esac } @@ -380,10 +387,16 @@ else init_merge_tool_path "$merge_tool" - if ! type "$merge_tool_path" > /dev/null 2>&1; then + merge_keep_backup="$(git config --bool merge.keepBackup || echo true)" + + if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then echo "The merge tool $merge_tool is not available as '$merge_tool_path'" exit 1 fi + + if ! test -z "$merge_tool_cmd"; then + merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)" + fi fi diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 233e5eae1d..7cd8f7134e 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -63,7 +63,23 @@ tmp_info="$tmp_dir/info" commit=$(git rev-parse HEAD) mkdir $tmp_dir || exit 2 -for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do +while read patch_name level garbage +do + case "$patch_name" in ''|'#'*) continue;; esac + case "$level" in + -p*) ;; + ''|'#'*) + level=;; + *) + echo "unable to parse patch level, ignoring it." + level=;; + esac + case "$garbage" in + ''|'#'*);; + *) + echo "trailing garbage found in series file: $garbage" + exit 1;; + esac if ! [ -f "$QUILT_PATCHES/$patch_name" ] ; then echo "$patch_name doesn't exist. Skipping." continue @@ -113,10 +129,10 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do fi if [ -z "$dry_run" ] ; then - git apply --index -C1 "$tmp_patch" && + git apply --index -C1 ${level:+"$level"} "$tmp_patch" && tree=$(git write-tree) && commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) && git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4 fi -done +done <"$QUILT_PATCHES/series" rm -rf $tmp_dir || exit 5 diff --git a/git-rebase.sh b/git-rebase.sh index 6b9af962a9..ff66af3ba8 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -18,8 +18,7 @@ original <branch> and remove the .dotest working files, use the command git rebase --abort instead. Note that if <branch> is not specified on the command line, the -currently checked out branch is used. You must be in the top -directory of your project to start (or continue) a rebase. +currently checked out branch is used. Example: git-rebase master~1 topic @@ -376,7 +375,7 @@ 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" && + git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? test 0 != $ret -a -d .dotest && diff --git a/git-send-email.perl b/git-send-email.perl index 29b1105c4c..be4a20d7cd 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -317,7 +317,7 @@ if ($suppress_cc{'all'}) { # If explicit old-style ones are specified, they trump --suppress-cc. $suppress_cc{'self'} = $suppress_from if defined $suppress_from; -$suppress_cc{'sob'} = $signed_off_cc if defined $signed_off_cc; +$suppress_cc{'sob'} = !$signed_off_cc if defined $signed_off_cc; # Debugging, print out the suppressions. if (0) { @@ -855,6 +855,7 @@ foreach my $t (@files) { $message .= $_; if (/^(Signed-off-by|Cc): (.*)$/i) { next if ($suppress_cc{'sob'}); + chomp; my $c = $2; chomp $c; next if ($c eq $sender and $suppress_cc{'self'}); diff --git a/git-stash.sh b/git-stash.sh index b00f888169..c2b68205a2 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -1,7 +1,7 @@ #!/bin/sh # Copyright (c) 2007, Nanako Shiraishi -USAGE='[ | save | list | show | apply | clear | create ]' +USAGE='[ | save | list | show | apply | clear | drop | pop | create ]' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= @@ -196,6 +196,28 @@ apply_stash () { fi } +drop_stash () { + have_stash || die 'No stash entries to drop' + + if test $# = 0 + then + set x "$ref_stash@{0}" + shift + fi + # Verify supplied argument looks like a stash entry + s=$(git rev-parse --revs-only --no-flags "$@") && + git rev-parse --verify "$s:" > /dev/null 2>&1 && + git rev-parse --verify "$s^1:" > /dev/null 2>&1 && + git rev-parse --verify "$s^2:" > /dev/null 2>&1 || + die "$*: not a valid stashed state" + + git reflog delete --updateref --rewrite "$@" && + echo "Dropped $* ($s)" || die "$*: Could not drop stash entry" + + # clear_stash if we just dropped the last stash entry + git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash +} + # Main command set case "$1" in list) @@ -230,6 +252,18 @@ create) fi create_stash "$*" && echo "$w_commit" ;; +drop) + shift + drop_stash "$@" + ;; +pop) + shift + if apply_stash "$@" + then + test -z "$unstash_index" || shift + drop_stash "$@" + fi + ;; *) if test $# -eq 0 then diff --git a/git-submodule.sh b/git-submodule.sh index 558a5ca107..5f1d5ef06e 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -155,20 +155,6 @@ cmd_add() usage 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 path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') @@ -176,15 +162,39 @@ cmd_add() path=$(echo "$path" | sed -e 's|/*$||') fi - test -e "$path" && - die "'$path' already exists" - git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" - module_clone "$path" "$realrepo" || exit - (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || - die "Unable to checkout submodule '$path'" + # perhaps the path exists and is already a git repo, else clone it + if test -e "$path" + then + if test -d "$path/.git" && + test "$(unset GIT_DIR; cd $path; git rev-parse --git-dir)" = ".git" + then + echo "Adding existing repo at '$path' to the index" + else + die "'$path' already exists and is not a valid git repo" + fi + else + 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 + + module_clone "$path" "$realrepo" || exit + (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || + die "Unable to checkout submodule '$path'" + fi + git add "$path" || die "Failed to add submodule '$path'" diff --git a/git-svn.perl b/git-svn.perl index 9e2faf90aa..bba22c1321 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -522,7 +522,8 @@ sub cmd_dcommit { } sub cmd_find_rev { - my $revision_or_hash = shift; + my $revision_or_hash = shift or die "SVN or git revision required ", + "as a command-line argument\n"; my $result; if ($revision_or_hash =~ /^r\d+$/) { my $head = shift; @@ -957,9 +958,10 @@ sub complete_url_ls_init { "wanted to set to: $gs->{url}\n"; } command_oneline('config', $k, $gs->{url}) unless $orig_url; - my $remote_path = "$ra->{svn_path}/$repo_path/*"; + my $remote_path = "$ra->{svn_path}/$repo_path"; $remote_path =~ s#/+#/#g; $remote_path =~ s#^/##g; + $remote_path .= "/*" if $remote_path !~ /\*/; my ($n) = ($switch =~ /^--(\w+)/); if (length $pfx && $pfx !~ m#/$#) { die "--prefix='$pfx' must have a trailing slash '/'\n"; @@ -1540,9 +1542,14 @@ sub find_by_url { # repos_root and, path are optional $remotes->{$repo_id}->{$_}); } my $p = $path; + my $rwr = rewrite_root({repo_id => $repo_id}); unless (defined $p) { $p = $full_url; - $p =~ s#^\Q$u\E(?:/|$)## or next; + my $z = $u; + if ($rwr) { + $z = $rwr; + } + $p =~ s#^\Q$z\E(?:/|$)## or next; } foreach my $f (keys %$fetch) { next if $f ne $p; diff --git a/git-web--browse.sh b/git-web--browse.sh index 1023b90859..384148a59f 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -23,12 +23,18 @@ USAGE='[--browser=browser|--tool=browser] [--config=conf.var] url/file ...' NONGIT_OK=Yes . git-sh-setup +valid_custom_tool() +{ + browser_cmd="$(git config "browser.$1.cmd")" + test -n "$browser_cmd" +} + valid_tool() { case "$1" in firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open) ;; # happy *) - return 1 + valid_custom_tool "$1" || return 1 ;; esac } @@ -122,7 +128,7 @@ else init_browser_path "$browser" - if ! type "$browser_path" > /dev/null 2>&1; then + if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then die "The browser $browser is not available as '$browser_path'." fi fi @@ -157,4 +163,9 @@ case "$browser" in dillo) "$browser_path" "$@" & ;; + *) + if test -n "$browser_cmd"; then + ( eval $browser_cmd "$@" ) + fi + ;; esac @@ -334,6 +334,7 @@ static void handle_internal_command(int argc, const char **argv) { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, { "reflog", cmd_reflog, RUN_SETUP }, + { "remote", cmd_remote, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, @@ -342,7 +343,7 @@ static void handle_internal_command(int argc, const char **argv) { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, - { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, + { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, diff --git a/gitk-git/Makefile b/gitk-git/Makefile index ae2b80b108..f90dfabffa 100644 --- a/gitk-git/Makefile +++ b/gitk-git/Makefile @@ -8,6 +8,7 @@ gitk_libdir ?= $(sharedir)/gitk/lib msgsdir ?= $(gitk_libdir)/msgs msgsdir_SQ = $(subst ','\'',$(msgsdir)) +TCL_PATH ?= tclsh TCLTK_PATH ?= wish INSTALL ?= install RM ?= rm -f @@ -22,6 +23,9 @@ ifdef NO_MSGFMT MSGFMT ?= $(TCL_PATH) po/po2msg.sh else MSGFMT ?= msgfmt + ifneq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null; echo $$?),0) + MSGFMT := $(TCL_PATH) po/po2msg.sh + endif endif PO_TEMPLATE = po/gitk.pot diff --git a/gitk-git/gitk b/gitk-git/gitk index f1f21e97bf..84ab02e15f 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -82,7 +82,7 @@ proc dorunq {} { proc start_rev_list {view} { global startmsecs global commfd leftover tclencoding datemode - global viewargs viewfiles commitidx viewcomplete vnextroot + global viewargs viewargscmd viewfiles commitidx viewcomplete vnextroot global showlocalchanges commitinterest mainheadid global progressdirn progresscoords proglastnc curview @@ -90,13 +90,23 @@ proc start_rev_list {view} { set commitidx($view) 0 set viewcomplete($view) 0 set vnextroot($view) 0 + set args $viewargs($view) + if {$viewargscmd($view) ne {}} { + if {[catch { + set str [exec sh -c $viewargscmd($view)] + } err]} { + error_popup "Error executing --argscmd command: $err" + exit 1 + } + set args [concat $args [split $str "\n"]] + } set order "--topo-order" if {$datemode} { set order "--date-order" } if {[catch { set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \ - --boundary $viewargs($view) "--" $viewfiles($view)] r] + --boundary $args "--" $viewfiles($view)] r] } err]} { error_popup "[mc "Error executing git rev-list:"] $err" exit 1 @@ -393,6 +403,9 @@ proc readcommit {id} { proc updatecommits {} { global viewdata curview phase displayorder ordertok idpending global children commitrow selectedline thickerline showneartags + global isworktree + + set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] if {$phase ne {}} { stop_rev_list @@ -827,6 +840,7 @@ proc makewindow {} { } frame .bleft.top frame .bleft.mid + frame .bleft.bottom button .bleft.top.search -text [mc "Search"] -command dosearch pack .bleft.top.search -side left -padx 5 @@ -854,18 +868,25 @@ proc makewindow {} { checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ -command changeignorespace -variable ignorespace pack .bleft.mid.ignspace -side left -padx 5 - set ctext .bleft.ctext + set ctext .bleft.bottom.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font textfont \ - -yscrollcommand scrolltext -wrap none + -yscrollcommand scrolltext -wrap none \ + -xscrollcommand ".bleft.bottom.sbhorizontal set" if {$have_tk85} { $ctext conf -tabstyle wordprocessor } - scrollbar .bleft.sb -command "$ctext yview" + scrollbar .bleft.bottom.sb -command "$ctext yview" + scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \ + -width 10 pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x - pack .bleft.sb -side right -fill y - pack $ctext -side left -fill both -expand 1 + grid $ctext .bleft.bottom.sb -sticky nsew + grid .bleft.bottom.sbhorizontal -sticky ew + grid columnconfigure .bleft.bottom 0 -weight 1 + grid rowconfigure .bleft.bottom 0 -weight 1 + grid rowconfigure .bleft.bottom 1 -weight 0 + pack .bleft.bottom -side top -fill both -expand 1 lappend bglist $ctext lappend fglist $ctext @@ -930,9 +951,17 @@ proc makewindow {} { .pwbottom add .bright .ctop add .pwbottom - # restore window position if known + # restore window width & height if known if {[info exists geometry(main)]} { - wm geometry . "$geometry(main)" + if {[scan $geometry(main) "%dx%d" w h] >= 2} { + if {$w > [winfo screenwidth .]} { + set w [winfo screenwidth .] + } + if {$h > [winfo screenheight .]} { + set h [winfo screenheight .] + } + wm geometry . "${w}x$h" + } } if {[tk windowingsystem] eq {aqua}} { @@ -1160,9 +1189,10 @@ proc savestuff {w} { global canv canv2 canv3 mainfont textfont uifont tabstop global stuffsaved findmergefiles maxgraphpct global maxwidth showneartags showlocalchanges - global viewname viewfiles viewargs viewperm nextviewnum + global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor + global autoselect if {$stuffsaved} return if {![winfo viewable .]} return @@ -1177,6 +1207,7 @@ proc savestuff {w} { puts $f [list set maxwidth $maxwidth] puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] + puts $f [list set autoselect $autoselect] puts $f [list set showneartags $showneartags] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] @@ -1199,7 +1230,7 @@ proc savestuff {w} { puts -nonewline $f "set permviews {" for {set v 0} {$v < $nextviewnum} {incr v} { if {$viewperm($v)} { - puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}" + puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}" } } puts $f "}" @@ -1850,7 +1881,7 @@ proc shellsplit {str} { proc newview {ishighlight} { global nextviewnum newviewname newviewperm newishighlight - global newviewargs revtreeargs + global newviewargs revtreeargs viewargscmd newviewargscmd curview set newishighlight $ishighlight set top .gitkview @@ -1858,16 +1889,17 @@ proc newview {ishighlight} { raise $top return } - set newviewname($nextviewnum) "View $nextviewnum" + set newviewname($nextviewnum) "[mc "View"] $nextviewnum" set newviewperm($nextviewnum) 0 set newviewargs($nextviewnum) [shellarglist $revtreeargs] + set newviewargscmd($nextviewnum) $viewargscmd($curview) vieweditor $top $nextviewnum [mc "Gitk view definition"] } proc editview {} { global curview global viewname viewperm newviewname newviewperm - global viewargs newviewargs + global viewargs newviewargs viewargscmd newviewargscmd set top .gitkvedit-$curview if {[winfo exists $top]} { @@ -1877,6 +1909,7 @@ proc editview {} { set newviewname($curview) $viewname($curview) set newviewperm($curview) $viewperm($curview) set newviewargs($curview) [shellarglist $viewargs($curview)] + set newviewargscmd($curview) $viewargscmd($curview) vieweditor $top $curview "Gitk: edit view $viewname($curview)" } @@ -1897,6 +1930,14 @@ proc vieweditor {top n title} { entry $top.args -width 50 -textvariable newviewargs($n) \ -background $bgcolor grid $top.args - -sticky ew -padx 5 + + message $top.ac -aspect 1000 \ + -text [mc "Command to generate more commits to include:"] + grid $top.ac - -sticky w -pady 5 + entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \ + -background white + grid $top.argscmd - -sticky ew -padx 5 + message $top.l -aspect 1000 \ -text [mc "Enter files and directories to include, one per line:"] grid $top.l - -sticky w @@ -1940,7 +1981,7 @@ proc allviewmenus {n op args} { proc newviewok {top n} { global nextviewnum newviewperm newviewname newishighlight global viewname viewfiles viewperm selectedview curview - global viewargs newviewargs viewhlmenu + global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu if {[catch { set newargs [shellsplit $newviewargs($n)] @@ -1964,6 +2005,7 @@ proc newviewok {top n} { set viewperm($n) $newviewperm($n) set viewfiles($n) $files set viewargs($n) $newargs + set viewargscmd($n) $newviewargscmd($n) addviewmenu $n if {!$newishighlight} { run showview $n @@ -1980,9 +2022,11 @@ proc newviewok {top n} { # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ # entryconf [list -label $viewname($n) -value $viewname($n)] } - if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} { + if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \ + $newviewargscmd($n) ne $viewargscmd($n)} { set viewfiles($n) $files set viewargs($n) $newargs + set viewargscmd($n) $newviewargscmd($n) if {$curview == $n} { run updatecommits } @@ -2058,8 +2102,6 @@ proc showview {n} { set ybot [expr {[lindex $span 1] * $ymax}] if {$ytop < $y && $y < $ybot} { set yscreen [expr {$y - $ytop}] - } else { - set yscreen [expr {($ybot - $ytop) / 2}] } } elseif {[info exists pending_select]} { set selid $pending_select @@ -2120,7 +2162,7 @@ proc showview {n} { set yf 0 set row {} set selectfirst 0 - if {$selid ne {} && [info exists commitrow($n,$selid)]} { + if {[info exists yscreen] && [info exists commitrow($n,$selid)]} { set row $commitrow($n,$selid) # try to get the selected row in the same position on the screen set ymax [lindex [$canv cget -scrollregion] 3] @@ -2844,8 +2886,9 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { global localirow localfrow lserial showlocalchanges + global isworktree - if {!$showlocalchanges} return + if {!$showlocalchanges || !$isworktree} return incr lserial set localfrow -1 set localirow -1 @@ -4650,6 +4693,7 @@ proc selectline {l isnew} { global commentend idtags linknum global mergemax numcommits pending_select global cmitmode showneartags allcommits + global autoselect catch {unset pending_select} $canv delete hover @@ -4705,8 +4749,10 @@ proc selectline {l isnew} { set currentid $id $sha1entry delete 0 end $sha1entry insert 0 $id - $sha1entry selection from 0 - $sha1entry selection to end + if {$autoselect} { + $sha1entry selection from 0 + $sha1entry selection to end + } rhighlight_sel $id $ctext conf -state normal @@ -5604,7 +5650,7 @@ proc searchmarkvisible {doall} { proc scrolltext {f0 f1} { global searchstring - .bleft.sb set $f0 $f1 + .bleft.bottom.sb set $f0 $f1 if {$searchstring ne {}} { searchmarkvisible 0 } @@ -7943,7 +7989,7 @@ proc doprefs {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor - global tabstop limitdiffs + global tabstop limitdiffs autoselect set top .gitkprefs set prefstop $top @@ -7973,6 +8019,11 @@ proc doprefs {} { checkbutton $top.showlocal.b -variable showlocalchanges pack $top.showlocal.b $top.showlocal.l -side left grid x $top.showlocal -sticky w + frame $top.autoselect + label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont + checkbutton $top.autoselect.b -variable autoselect + pack $top.autoselect.b $top.autoselect.l -side left + grid x $top.autoselect -sticky w label $top.ddisp -text [mc "Diff display options"] grid $top.ddisp - -sticky w -pady 10 @@ -8463,6 +8514,7 @@ set maxlinelen 200 set showlocalchanges 1 set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" +set autoselect 1 set colors {green red blue magenta darkgrey brown orange} set bgcolor white @@ -8522,8 +8574,9 @@ set mergeonly 0 set revtreeargs {} set cmdline_files {} set i 0 +set revtreeargscmd {} foreach arg $argv { - switch -- $arg { + switch -glob -- $arg { "" { } "-d" { set datemode 1 } "--merge" { @@ -8534,6 +8587,9 @@ foreach arg $argv { set cmdline_files [lrange $argv [expr {$i + 1}] end] break } + "--argscmd=*" { + set revtreeargscmd [string range $arg 10 end] + } default { lappend revtreeargs $arg } @@ -8635,6 +8691,7 @@ set highlight_files {} set viewfiles(0) {} set viewperm(0) 0 set viewargs(0) {} +set viewargscmd(0) {} set cmdlineok 0 set stopped 0 @@ -8643,6 +8700,7 @@ set patchnum 0 set localirow -1 set localfrow -1 set lserial 0 +set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] setcoords makewindow # wait for the window to become visible @@ -8650,7 +8708,7 @@ tkwait visibility . wm title . "[file tail $argv0]: [file tail [pwd]]" readrefs -if {$cmdline_files ne {} || $revtreeargs ne {}} { +if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} { # create a view for the files/dirs specified on the command line set curview 1 set selectedview 1 @@ -8658,6 +8716,7 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} { set viewname(1) [mc "Command line"] set viewfiles(1) $cmdline_files set viewargs(1) $revtreeargs + set viewargscmd(1) $revtreeargscmd set viewperm(1) 0 addviewmenu 1 .bar.view entryconf [mc "Edit view..."] -state normal @@ -8671,6 +8730,7 @@ if {[info exists permviews]} { set viewname($n) [lindex $v 0] set viewfiles($n) [lindex $v 1] set viewargs($n) [lindex $v 2] + set viewargscmd($n) [lindex $v 3] set viewperm($n) 1 addviewmenu $n } diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po new file mode 100644 index 0000000000..d0f4c2e19a --- /dev/null +++ b/gitk-git/po/it.po @@ -0,0 +1,890 @@ +# Translation of gitk +# Copyright (C) 2005-2008 Paul Mackerras +# This file is distributed under the same license as the gitk package. +# Michele Ballabio <barra_cuda@katamail.com>, 2008. +# +# +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-03-13 17:29+0100\n" +"PO-Revision-Date: 2008-03-13 17:34+0100\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" + +#: gitk:111 +msgid "Error executing git rev-list:" +msgstr "Errore nell'esecuzione di git rev-list:" + +#: gitk:124 +msgid "Reading" +msgstr "Lettura in corso" + +#: gitk:151 gitk:2191 +msgid "Reading commits..." +msgstr "Lettura delle revisioni in corso..." + +#: gitk:275 +msgid "Can't parse git log output:" +msgstr "Impossibile elaborare i dati di git log:" + +#: gitk:386 gitk:2195 +msgid "No commits selected" +msgstr "Nessuna revisione selezionata" + +#: gitk:500 +msgid "No commit information available" +msgstr "Nessuna informazione disponibile sulle revisioni" + +#: gitk:599 gitk:621 gitk:1955 gitk:6423 gitk:7923 gitk:8082 +msgid "OK" +msgstr "OK" + +#: gitk:623 gitk:1956 gitk:6107 gitk:6178 gitk:6275 gitk:6321 gitk:6425 +#: gitk:7924 gitk:8083 +msgid "Cancel" +msgstr "Annulla" + +#: gitk:661 +msgid "File" +msgstr "File" + +#: gitk:663 +msgid "Update" +msgstr "Aggiorna" + +#: gitk:664 +msgid "Reread references" +msgstr "Rileggi riferimenti" + +#: gitk:665 +msgid "List references" +msgstr "Elenca riferimenti" + +#: gitk:666 +msgid "Quit" +msgstr "Esci" + +#: gitk:668 +msgid "Edit" +msgstr "Modifica" + +#: gitk:669 +msgid "Preferences" +msgstr "Preferenze" + +#: gitk:672 gitk:1892 +msgid "View" +msgstr "Vista" + +#: gitk:673 +msgid "New view..." +msgstr "Nuova vista..." + +#: gitk:674 gitk:2133 gitk:8722 +msgid "Edit view..." +msgstr "Modifica vista..." + +#: gitk:676 gitk:2134 gitk:8723 +msgid "Delete view" +msgstr "Elimina vista" + +#: gitk:678 +msgid "All files" +msgstr "Tutti i file" + +#: gitk:682 +msgid "Help" +msgstr "Aiuto" + +#: gitk:683 gitk:1317 +msgid "About gitk" +msgstr "Informazioni su gitk" + +#: gitk:684 +msgid "Key bindings" +msgstr "Scorciatoie da tastiera" + +#: gitk:741 +msgid "SHA1 ID: " +msgstr "SHA1 ID: " + +#: gitk:791 +msgid "Find" +msgstr "Trova" + +#: gitk:792 +msgid "next" +msgstr "succ" + +#: gitk:793 +msgid "prev" +msgstr "prec" + +#: gitk:794 +msgid "commit" +msgstr "revisione" + +#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369 +msgid "containing:" +msgstr "contenente:" + +#: gitk:800 gitk:1778 gitk:1783 gitk:2431 +msgid "touching paths:" +msgstr "che riguarda i percorsi:" + +#: gitk:801 gitk:2436 +msgid "adding/removing string:" +msgstr "che aggiunge/rimuove la stringa:" + +#: gitk:810 gitk:812 +msgid "Exact" +msgstr "Esatto" + +#: gitk:812 gitk:2514 gitk:4274 +msgid "IgnCase" +msgstr "" + +#: gitk:812 gitk:2405 gitk:2512 gitk:4270 +msgid "Regexp" +msgstr "" + +#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436 +msgid "All fields" +msgstr "Tutti i campi" + +#: gitk:815 gitk:2531 gitk:2563 gitk:4336 +msgid "Headline" +msgstr "Titolo" + +#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827 +msgid "Comments" +msgstr "Commenti" + +#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5956 +#: gitk:5971 +msgid "Author" +msgstr "Autore" + +#: gitk:816 gitk:2531 gitk:4336 gitk:4765 +msgid "Committer" +msgstr "Revisione creata da" + +#: gitk:845 +msgid "Search" +msgstr "Cerca" + +#: gitk:852 +msgid "Diff" +msgstr "" + +#: gitk:854 +msgid "Old version" +msgstr "Vecchia versione" + +#: gitk:856 +msgid "New version" +msgstr "Nuova versione" + +#: gitk:858 +msgid "Lines of context" +msgstr "Linee di contesto" + +#: gitk:868 +msgid "Ignore space change" +msgstr "Ignora modifiche agli spazi" + +#: gitk:926 +msgid "Patch" +msgstr "Modifiche" + +#: gitk:928 +msgid "Tree" +msgstr "Directory" + +#: gitk:1053 gitk:1068 gitk:6022 +msgid "Diff this -> selected" +msgstr "Diff questo -> selezionato" + +#: gitk:1055 gitk:1070 gitk:6023 +msgid "Diff selected -> this" +msgstr "Diff selezionato -> questo" + +#: gitk:1057 gitk:1072 gitk:6024 +msgid "Make patch" +msgstr "Crea patch" + +#: gitk:1058 gitk:6162 +msgid "Create tag" +msgstr "Crea etichetta" + +#: gitk:1059 gitk:6255 +msgid "Write commit to file" +msgstr "Scrivi revisione in un file" + +#: gitk:1060 gitk:6309 +msgid "Create new branch" +msgstr "Crea un nuovo ramo" + +#: gitk:1061 +msgid "Cherry-pick this commit" +msgstr "Porta questa revisione in cima al ramo attuale" + +#: gitk:1063 +msgid "Reset HEAD branch to here" +msgstr "Aggiorna il ramo HEAD a questa revisione" + +#: gitk:1079 +msgid "Check out this branch" +msgstr "Attiva questo ramo" + +#: gitk:1081 +msgid "Remove this branch" +msgstr "Elimina questo ramo" + +#: gitk:1087 +msgid "Highlight this too" +msgstr "Evidenzia anche questo" + +#: gitk:1089 +msgid "Highlight this only" +msgstr "Evidenzia solo questo" + +#: gitk:1318 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2006 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - un visualizzatore di revisioni per git\n" +"\n" +"Copyright © 2005-2006 Paul Mackerras\n" +"\n" +"Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " +"License" + +#: gitk:1326 gitk:1387 gitk:6581 +msgid "Close" +msgstr "Chiudi" + +#: gitk:1345 +msgid "Gitk key bindings" +msgstr "Scorciatoie da tastiera di Gitk" + +#: gitk:1347 +msgid "Gitk key bindings:" +msgstr "Scorciatoie da tastiera di Gitk:" + +#: gitk:1349 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tEsci" + +#: gitk:1350 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\tVai alla prima revisione" + +#: gitk:1351 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\tVai all'ultima revisione" + +#: gitk:1352 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Up>, p, i\tVai più in alto di una revisione" + +#: gitk:1353 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Down>, n, k\tVai più in basso di una revisione" + +#: gitk:1354 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Left>, z, j\tTorna indietro nella cronologia" + +#: gitk:1355 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Right>, x, l\tVai avanti nella cronologia" + +#: gitk:1356 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tVai più in alto di una pagina nella lista delle revisioni" + +#: gitk:1357 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tVai più in basso di una pagina nella lista delle revisioni" + +#: gitk:1358 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tScorri alla cima della lista delle revisioni" + +#: gitk:1359 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tScorri alla fine della lista delle revisioni" + +#: gitk:1360 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tScorri la lista delle revisioni in alto di una riga" + +#: gitk:1361 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tScorri la lista delle revisioni in basso di una riga" + +#: gitk:1362 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tScorri la lista delle revisioni in alto di una pagina" + +#: gitk:1363 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tScorri la lista delle revisioni in basso di una pagina" + +#: gitk:1364 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Shift-Up>\tTrova all'indietro (verso l'alto, revisioni successive)" + +#: gitk:1365 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Shift-Down>\tTrova in avanti (verso il basso, revisioni precedenti)" + +#: gitk:1366 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tScorri la vista delle differenze in alto di una pagina" + +#: gitk:1367 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tScorri la vista delle differenze in alto di una pagina" + +#: gitk:1368 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Space>\t\tScorri la vista delle differenze in basso di una pagina" + +#: gitk:1369 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tScorri la vista delle differenze in alto di 18 linee" + +#: gitk:1370 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tScorri la vista delle differenze in basso di 18 linee" + +#: gitk:1371 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tTrova" + +#: gitk:1372 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tTrova in avanti" + +#: gitk:1373 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\tTrova in avanti" + +#: gitk:1374 +msgid "/\t\tMove to next find hit, or redo find" +msgstr "/\t\tTrova in avanti, o cerca di nuovo" + +#: gitk:1375 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tTrova all'indietro" + +#: gitk:1376 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tScorri la vista delle differenze al file successivo" + +#: gitk:1377 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tCerca in avanti nella vista delle differenze" + +#: gitk:1378 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tCerca all'indietro nella vista delle differenze" + +#: gitk:1379 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tAumenta grandezza carattere" + +#: gitk:1380 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\tAumenta grandezza carattere" + +#: gitk:1381 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tDiminuisci grandezza carattere" + +#: gitk:1382 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\tDiminuisci grandezza carattere" + +#: gitk:1383 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tAggiorna" + +#: gitk:1896 +msgid "Gitk view definition" +msgstr "Scelta vista Gitk" + +#: gitk:1921 +msgid "Name" +msgstr "Nome" + +#: gitk:1924 +msgid "Remember this view" +msgstr "Ricorda questa vista" + +#: gitk:1928 +msgid "Commits to include (arguments to git rev-list):" +msgstr "Revisioni da includere (argomenti di git rev-list):" + +#: gitk:1935 +msgid "Command to generate more commits to include:" +msgstr "Comando che genera altre revisioni da visualizzare:" + +#: gitk:1942 +msgid "Enter files and directories to include, one per line:" +msgstr "Inserire file e directory da includere, uno per riga:" + +#: gitk:1989 +msgid "Error in commit selection arguments:" +msgstr "Errore negli argomenti di selezione delle revisioni:" + +#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8688 gitk:8689 +msgid "None" +msgstr "Nessuno" + +#: gitk:2531 gitk:4336 gitk:5958 gitk:5973 +msgid "Date" +msgstr "Data" + +#: gitk:2531 gitk:4336 +msgid "CDate" +msgstr "" + +#: gitk:2680 gitk:2685 +msgid "Descendant" +msgstr "Discendente" + +#: gitk:2681 +msgid "Not descendant" +msgstr "Non discendente" + +#: gitk:2688 gitk:2693 +msgid "Ancestor" +msgstr "Ascendente" + +#: gitk:2689 +msgid "Not ancestor" +msgstr "Non ascendente" + +#: gitk:2924 +msgid "Local changes checked in to index but not committed" +msgstr "Modifiche locali presenti nell'indice ma non nell'archivio" + +#: gitk:2954 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Modifiche locali non presenti né nell'archivio né nell'indice" + +#: gitk:4305 +msgid "Searching" +msgstr "Ricerca in corso" + +#: gitk:4767 +msgid "Tags:" +msgstr "Etichette:" + +#: gitk:4784 gitk:4790 gitk:5951 +msgid "Parent" +msgstr "Genitore" + +#: gitk:4795 +msgid "Child" +msgstr "Figlio" + +#: gitk:4804 +msgid "Branch" +msgstr "Ramo" + +#: gitk:4807 +msgid "Follows" +msgstr "Segue" + +#: gitk:4810 +msgid "Precedes" +msgstr "Precede" + +#: gitk:5093 +msgid "Error getting merge diffs:" +msgstr "Errore nella lettura delle differenze di fusione:" + +#: gitk:5778 +msgid "Goto:" +msgstr "Vai a:" + +#: gitk:5780 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:5805 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "La SHA1 id abbreviata %s è ambigua" + +#: gitk:5817 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "La SHA1 id %s è sconosciuta" + +#: gitk:5819 +#, tcl-format +msgid "Tag/Head %s is not known" +msgstr "L'etichetta/ramo %s è sconosciuto" + +#: gitk:5961 +msgid "Children" +msgstr "Figli" + +#: gitk:6018 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Aggiorna il ramo %s a questa revisione" + +#: gitk:6049 +msgid "Top" +msgstr "Inizio" + +#: gitk:6050 +msgid "From" +msgstr "Da" + +#: gitk:6055 +msgid "To" +msgstr "A" + +#: gitk:6078 +msgid "Generate patch" +msgstr "Genera patch" + +#: gitk:6080 +msgid "From:" +msgstr "Da:" + +#: gitk:6089 +msgid "To:" +msgstr "A:" + +#: gitk:6098 +msgid "Reverse" +msgstr "Inverti" + +#: gitk:6100 gitk:6269 +msgid "Output file:" +msgstr "Scrivi sul file:" + +#: gitk:6106 +msgid "Generate" +msgstr "Genera" + +#: gitk:6142 +msgid "Error creating patch:" +msgstr "Errore nella creazione della patch:" + +#: gitk:6164 gitk:6257 gitk:6311 +msgid "ID:" +msgstr "ID:" + +#: gitk:6173 +msgid "Tag name:" +msgstr "Nome etichetta:" + +#: gitk:6177 gitk:6320 +msgid "Create" +msgstr "Crea" + +#: gitk:6192 +msgid "No tag name specified" +msgstr "Nessuna etichetta specificata" + +#: gitk:6196 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "L'etichetta \"%s\" esiste già" + +#: gitk:6202 +msgid "Error creating tag:" +msgstr "Errore nella creazione dell'etichetta:" + +#: gitk:6266 +msgid "Command:" +msgstr "Comando:" + +#: gitk:6274 +msgid "Write" +msgstr "Scrivi" + +#: gitk:6290 +msgid "Error writing commit:" +msgstr "Errore nella scrittura della revisione:" + +#: gitk:6316 +msgid "Name:" +msgstr "Nome:" + +#: gitk:6335 +msgid "Please specify a name for the new branch" +msgstr "Specificare un nome per il nuovo ramo" + +#: gitk:6364 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?" + +#: gitk:6369 +msgid "Cherry-picking" +msgstr "" + +#: gitk:6381 +msgid "No changes committed" +msgstr "Nessuna modifica archiviata" + +#: gitk:6404 +msgid "Confirm reset" +msgstr "Conferma git reset" + +#: gitk:6406 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Aggiornare il ramo %s a %s?" + +#: gitk:6410 +msgid "Reset type:" +msgstr "Tipo di aggiornamento:" + +#: gitk:6414 +msgid "Soft: Leave working tree and index untouched" +msgstr "Soft: Lascia la direcory di lavoro e l'indice come sono" + +#: gitk:6417 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Mixed: Lascia la directory di lavoro come è, aggiorna l'indice" + +#: gitk:6420 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: Aggiorna la directory di lavoro e l'indice\n" +"(abbandona TUTTE le modifiche locali)" + +#: gitk:6436 +msgid "Resetting" +msgstr "git reset in corso" + +#: gitk:6493 +msgid "Checking out" +msgstr "Attivazione in corso" + +#: gitk:6523 +msgid "Cannot delete the currently checked-out branch" +msgstr "Impossibile cancellare il ramo attualmente attivo" + +#: gitk:6529 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"Le revisioni nel ramo %s non sono presenti su altri rami.\n" +"Cancellare il ramo %s?" + +#: gitk:6560 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Etichette e rami: %s" + +#: gitk:6574 +msgid "Filter" +msgstr "Filtro" + +#: gitk:6868 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Errore nella lettura della topologia delle revisioni: le informazioni sul " +"ramo e le etichette precedenti e seguenti saranno incomplete." + +#: gitk:7852 +msgid "Tag" +msgstr "Etichetta" + +#: gitk:7852 +msgid "Id" +msgstr "Id" + +#: gitk:7892 +msgid "Gitk font chooser" +msgstr "Scelta caratteri gitk" + +#: gitk:7909 +msgid "B" +msgstr "B" + +#: gitk:7912 +msgid "I" +msgstr "I" + +#: gitk:8005 +msgid "Gitk preferences" +msgstr "Preferenze gitk" + +#: gitk:8006 +msgid "Commit list display options" +msgstr "Opzioni visualizzazione dell'elenco revisioni" + +#: gitk:8009 +msgid "Maximum graph width (lines)" +msgstr "Larghezza massima del grafico (in linee)" + +#: gitk:8013 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Larghezza massima del grafico (% del pannello)" + +#: gitk:8018 +msgid "Show local changes" +msgstr "Mostra modifiche locali" + +#: gitk:8023 +msgid "Auto-select SHA1" +msgstr "Seleziona automaticamente SHA1 hash" + +#: gitk:8028 +msgid "Diff display options" +msgstr "Opzioni di visualizzazione delle differenze" + +#: gitk:8030 +msgid "Tab spacing" +msgstr "Spaziatura tabulazioni" + +#: gitk:8034 +msgid "Display nearby tags" +msgstr "Mostra etichette vicine" + +#: gitk:8039 +msgid "Limit diffs to listed paths" +msgstr "Limita le differenze ai percorsi elencati" + +#: gitk:8044 +msgid "Colors: press to choose" +msgstr "Colori: premere per scegliere" + +#: gitk:8047 +msgid "Background" +msgstr "Sfondo" + +#: gitk:8051 +msgid "Foreground" +msgstr "Primo piano" + +#: gitk:8055 +msgid "Diff: old lines" +msgstr "Diff: vecchie linee" + +#: gitk:8060 +msgid "Diff: new lines" +msgstr "Diff: nuove linee" + +#: gitk:8065 +msgid "Diff: hunk header" +msgstr "Diff: intestazione della sezione" + +#: gitk:8071 +msgid "Select bg" +msgstr "Sfondo selezione" + +#: gitk:8075 +msgid "Fonts: press to choose" +msgstr "Carattere: premere per scegliere" + +#: gitk:8077 +msgid "Main font" +msgstr "Carattere principale" + +#: gitk:8078 +msgid "Diff display font" +msgstr "Carattere per differenze" + +#: gitk:8079 +msgid "User interface font" +msgstr "Carattere per interfaccia utente" + +#: gitk:8095 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: scegliere un colore per %s" + +#: gitk:8476 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"Questa versione di Tcl/Tk non può avviare gitk.\n" +" Gitk richiede Tcl/Tk versione 8.4 o superiore." + +#: gitk:8565 +msgid "Cannot find a git repository here." +msgstr "Archivio git non trovato." + +#: gitk:8569 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "Directory git \"%s\" non trovata." + +#: gitk:8612 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Argomento ambiguo: '%s' è sia revisione che nome di file" + +#: gitk:8624 +msgid "Bad arguments to gitk:" +msgstr "Gitk: argomenti errati:" + +#: gitk:8636 +msgid "Couldn't get list of unmerged files:" +msgstr "Impossibile ottenere l'elenco dei file in attesa di fusione:" + +#: gitk:8652 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Nessun file selezionato: è stata specificata l'opzione --merge ma non ci " +"sono file in attesa di fusione." + +#: gitk:8655 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Nessun file selezionato: è stata specificata l'opzione --merge ma i file " +"specificati non sono in attesa di fusione." + +#: gitk:8716 +msgid "Command line" +msgstr "Linea di comando" diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 922dee98b9..ec73cb1256 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5305,51 +5305,19 @@ sub git_search { print "<table class=\"pickaxe search\">\n"; my $alternate = 1; $/ = "\n"; - my $git_command = git_cmd_str(); - my $searchqtext = $searchtext; - $searchqtext =~ s/'/'\\''/; - my $pickaxe_flags = $search_use_regexp ? '--pickaxe-regex' : ''; - open my $fd, "-|", "$git_command rev-list $hash | " . - "$git_command diff-tree -r --stdin -S\'$searchqtext\' $pickaxe_flags"; + open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts, + '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext", + ($search_use_regexp ? '--pickaxe-regex' : ()); undef %co; my @files; while (my $line = <$fd>) { - if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - my %set; - $set{'file'} = $6; - $set{'from_id'} = $3; - $set{'to_id'} = $4; - $set{'id'} = $set{'to_id'}; - if ($set{'id'} =~ m/0{40}/) { - $set{'id'} = $set{'from_id'}; - } - if ($set{'id'} =~ m/0{40}/) { - next; - } - push @files, \%set; - } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){ + chomp $line; + next unless $line; + + my %set = parse_difftree_raw_line($line); + if (defined $set{'commit'}) { + # finish previous commit if (%co) { - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - 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>" . $author . "</i></td>\n" . - "<td>" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), - -class => "list subject"}, - 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'}, - hash=>$set{'id'}, file_name=>$set{'file'}), - -class => "list"}, - "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") . - "<br/>\n"; - } print "</td>\n" . "<td class=\"link\">" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . @@ -5358,11 +5326,44 @@ sub git_search { print "</td>\n" . "</tr>\n"; } - %co = parse_commit($1); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + %co = parse_commit($set{'commit'}); + 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>$author</i></td>\n" . + "<td>" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), + -class => "list subject"}, + chop_and_escape_str($co{'title'}, 50) . "<br/>"); + } elsif (defined $set{'to_id'}) { + next if ($set{'to_id'} =~ m/^0{40}$/); + + print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, + hash=>$set{'to_id'}, file_name=>$set{'to_file'}), + -class => "list"}, + "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") . + "<br/>\n"; } } close $fd; + # finish last commit (warning: repetition!) + if (%co) { + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + print "</td>\n" . + "</tr>\n"; + } + print "</table>\n"; } @@ -9,7 +9,7 @@ * 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) +static struct hash_table_entry *lookup_hash_entry(unsigned int hash, const struct hash_table *table) { unsigned int size = table->size, nr = hash % size; struct hash_table_entry *array = table->array; @@ -66,7 +66,7 @@ static void grow_hash_table(struct hash_table *table) free(old_array); } -void *lookup_hash(unsigned int hash, struct hash_table *table) +void *lookup_hash(unsigned int hash, const struct hash_table *table) { if (!table->array) return NULL; @@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) return insert_hash_entry(hash, ptr, table); } -int for_each_hash(struct hash_table *table, int (*fn)(void *)) +int for_each_hash(const struct hash_table *table, int (*fn)(void *)) { int sum = 0; unsigned int i; @@ -28,9 +28,9 @@ struct hash_table { struct hash_table_entry *array; }; -extern void *lookup_hash(unsigned int hash, struct hash_table *table); +extern void *lookup_hash(unsigned int hash, const 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 int for_each_hash(const struct hash_table *table, int (*fn)(void *)); extern void free_hash(struct hash_table *table); static inline void init_hash(struct hash_table *table) @@ -8,6 +8,12 @@ #include "exec_cmd.h" #include "common-cmds.h" #include "parse-options.h" +#include "run-command.h" + +static struct man_viewer_list { + void (*exec)(const char *); + struct man_viewer_list *next; +} *man_viewer_list; enum help_format { HELP_FORMAT_MAN, @@ -42,6 +48,102 @@ static enum help_format parse_help_format(const char *format) die("unrecognized help format '%s'", format); } +static int check_emacsclient_version(void) +{ + struct strbuf buffer = STRBUF_INIT; + struct child_process ec_process; + const char *argv_ec[] = { "emacsclient", "--version", NULL }; + int version; + + /* emacsclient prints its version number on stderr */ + memset(&ec_process, 0, sizeof(ec_process)); + ec_process.argv = argv_ec; + ec_process.err = -1; + ec_process.stdout_to_stderr = 1; + if (start_command(&ec_process)) { + fprintf(stderr, "Failed to start emacsclient.\n"); + return -1; + } + strbuf_read(&buffer, ec_process.err, 20); + close(ec_process.err); + + /* + * Don't bother checking return value, because "emacsclient --version" + * seems to always exits with code 1. + */ + finish_command(&ec_process); + + if (prefixcmp(buffer.buf, "emacsclient")) { + fprintf(stderr, "Failed to parse emacsclient version.\n"); + strbuf_release(&buffer); + return -1; + } + + strbuf_remove(&buffer, 0, strlen("emacsclient")); + version = atoi(buffer.buf); + + if (version < 22) { + fprintf(stderr, + "emacsclient version '%d' too old (< 22).\n", + version); + strbuf_release(&buffer); + return -1; + } + + strbuf_release(&buffer); + return 0; +} + +static void exec_woman_emacs(const char *page) +{ + if (!check_emacsclient_version()) { + /* This works only with emacsclient version >= 22. */ + struct strbuf man_page = STRBUF_INIT; + strbuf_addf(&man_page, "(woman \"%s\")", page); + execlp("emacsclient", "emacsclient", "-e", man_page.buf, NULL); + } +} + +static void exec_man_konqueror(const char *page) +{ + const char *display = getenv("DISPLAY"); + if (display && *display) { + struct strbuf man_page = STRBUF_INIT; + strbuf_addf(&man_page, "man:%s(1)", page); + execlp("kfmclient", "kfmclient", "newTab", man_page.buf, NULL); + } +} + +static void exec_man_man(const char *page) +{ + execlp("man", "man", page, NULL); +} + +static void do_add_man_viewer(void (*exec)(const char *)) +{ + struct man_viewer_list **p = &man_viewer_list; + + while (*p) + p = &((*p)->next); + *p = xmalloc(sizeof(**p)); + (*p)->next = NULL; + (*p)->exec = exec; +} + +static int add_man_viewer(const char *value) +{ + if (!strcasecmp(value, "man")) + do_add_man_viewer(exec_man_man); + else if (!strcasecmp(value, "woman")) + do_add_man_viewer(exec_woman_emacs); + else if (!strcasecmp(value, "konqueror")) + do_add_man_viewer(exec_man_konqueror); + else + warning("'%s': unsupported man viewer.", value); + + return 0; +} + static int git_help_config(const char *var, const char *value) { if (!strcmp(var, "help.format")) { @@ -50,6 +152,11 @@ static int git_help_config(const char *var, const char *value) help_format = parse_help_format(value); return 0; } + if (!strcmp(var, "man.viewer")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer(value); + } return git_default_config(var, value); } @@ -347,9 +454,16 @@ static void setup_man_path(void) static void show_man_page(const char *git_cmd) { + struct man_viewer_list *viewer; const char *page = cmd_to_page(git_cmd); + setup_man_path(); - execlp("man", "man", page, NULL); + for (viewer = man_viewer_list; viewer; viewer = viewer->next) + { + viewer->exec(page); /* will return when unable */ + } + exec_man_man(page); + die("no man viewer handled the request"); } static void show_info_page(const char *git_cmd) @@ -284,23 +284,15 @@ void http_init(struct remote *remote) void http_cleanup(void) { struct active_request_slot *slot = active_queue_head; -#ifdef USE_CURL_MULTI - char *wait_url; -#endif while (slot != NULL) { struct active_request_slot *next = slot->next; + if (slot->curl != NULL) { #ifdef USE_CURL_MULTI - if (slot->in_use) { - curl_easy_getinfo(slot->curl, - CURLINFO_EFFECTIVE_URL, - &wait_url); - fprintf(stderr, "Waiting for %s\n", wait_url); - run_active_slot(slot); - } + curl_multi_remove_handle(curlm, slot->curl); #endif - if (slot->curl != NULL) curl_easy_cleanup(slot->curl); + } free(slot); slot = next; } @@ -171,7 +171,7 @@ static const char au_env[] = "GIT_AUTHOR_NAME"; static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = "\n" -"*** Your name cannot be determined from your system services (gecos).\n" +"*** Please tell me who you are.\n" "\n" "Run\n" "\n" diff --git a/ll-merge.c b/ll-merge.c new file mode 100644 index 0000000000..5ae74331bc --- /dev/null +++ b/ll-merge.c @@ -0,0 +1,379 @@ +/* + * Low level 3-way in-core file merge. + * + * Copyright (c) 2007 Junio C Hamano + */ + +#include "cache.h" +#include "attr.h" +#include "xdiff-interface.h" +#include "run-command.h" +#include "interpolate.h" +#include "ll-merge.h" + +struct ll_merge_driver; + +typedef int (*ll_merge_fn)(const struct ll_merge_driver *, + mmbuffer_t *result, + const char *path, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor); + +struct ll_merge_driver { + const char *name; + const char *description; + ll_merge_fn fn; + const char *recursive; + struct ll_merge_driver *next; + char *cmdline; +}; + +/* + * Built-in low-levels + */ +static int ll_binary_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + /* + * The tentative merge result is "ours" for the final round, + * or common ancestor for an internal merge. Still return + * "conflicted merge" status. + */ + mmfile_t *stolen = virtual_ancestor ? orig : src1; + + result->ptr = stolen->ptr; + result->size = stolen->size; + stolen->ptr = NULL; + return 1; +} + +static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + xpparam_t xpp; + + if (buffer_is_binary(orig->ptr, orig->size) || + buffer_is_binary(src1->ptr, src1->size) || + buffer_is_binary(src2->ptr, src2->size)) { + warning("Cannot merge binary files: %s vs. %s\n", + name1, name2); + return ll_binary_merge(drv_unused, result, + path_unused, + orig, src1, name1, + src2, name2, + virtual_ancestor); + } + + memset(&xpp, 0, sizeof(xpp)); + return xdl_merge(orig, + src1, name1, + src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + result); +} + +static int ll_union_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + char *src, *dst; + long size; + const int marker_size = 7; + + int status = ll_xdl_merge(drv_unused, result, path_unused, + orig, src1, NULL, src2, NULL, + virtual_ancestor); + if (status <= 0) + return status; + size = result->size; + src = dst = result->ptr; + while (size) { + char ch; + if ((marker_size < size) && + (*src == '<' || *src == '=' || *src == '>')) { + int i; + ch = *src; + for (i = 0; i < marker_size; i++) + if (src[i] != ch) + goto not_a_marker; + if (src[marker_size] != '\n') + goto not_a_marker; + src += marker_size + 1; + size -= marker_size + 1; + continue; + } + not_a_marker: + do { + ch = *src++; + *dst++ = ch; + size--; + } while (ch != '\n' && size); + } + result->size = dst - result->ptr; + return 0; +} + +#define LL_BINARY_MERGE 0 +#define LL_TEXT_MERGE 1 +#define LL_UNION_MERGE 2 +static struct ll_merge_driver ll_merge_drv[] = { + { "binary", "built-in binary merge", ll_binary_merge }, + { "text", "built-in 3-way text merge", ll_xdl_merge }, + { "union", "built-in union merge", ll_union_merge }, +}; + +static void create_temp(mmfile_t *src, char *path) +{ + int fd; + + strcpy(path, ".merge_file_XXXXXX"); + fd = xmkstemp(path); + if (write_in_full(fd, src->ptr, src->size) != src->size) + die("unable to write temp-file"); + close(fd); +} + +/* + * User defined low-level merge driver support. + */ +static int ll_ext_merge(const struct ll_merge_driver *fn, + mmbuffer_t *result, + const char *path, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + char temp[3][50]; + char cmdbuf[2048]; + struct interp table[] = { + { "%O" }, + { "%A" }, + { "%B" }, + }; + struct child_process child; + const char *args[20]; + int status, fd, i; + struct stat st; + + if (fn->cmdline == NULL) + die("custom merge driver %s lacks command line.", fn->name); + + result->ptr = NULL; + result->size = 0; + create_temp(orig, temp[0]); + create_temp(src1, temp[1]); + create_temp(src2, temp[2]); + + interp_set_entry(table, 0, temp[0]); + interp_set_entry(table, 1, temp[1]); + interp_set_entry(table, 2, temp[2]); + + interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3); + + memset(&child, 0, sizeof(child)); + child.argv = args; + args[0] = "sh"; + args[1] = "-c"; + args[2] = cmdbuf; + args[3] = NULL; + + status = run_command(&child); + if (status < -ERR_RUN_COMMAND_FORK) + ; /* failure in run-command */ + else + status = -status; + fd = open(temp[1], O_RDONLY); + if (fd < 0) + goto bad; + if (fstat(fd, &st)) + goto close_bad; + result->size = st.st_size; + result->ptr = xmalloc(result->size + 1); + if (read_in_full(fd, result->ptr, result->size) != result->size) { + free(result->ptr); + result->ptr = NULL; + result->size = 0; + } + close_bad: + close(fd); + bad: + for (i = 0; i < 3; i++) + unlink(temp[i]); + return status; +} + +/* + * merge.default and merge.driver configuration items + */ +static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; +static const char *default_ll_merge; + +static int read_merge_config(const char *var, const char *value) +{ + struct ll_merge_driver *fn; + const char *ep, *name; + int namelen; + + if (!strcmp(var, "merge.default")) { + if (value) + default_ll_merge = strdup(value); + return 0; + } + + /* + * We are not interested in anything but "merge.<name>.variable"; + * especially, we do not want to look at variables such as + * "merge.summary", "merge.tool", and "merge.verbosity". + */ + if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5) + return 0; + + /* + * Find existing one as we might be processing merge.<name>.var2 + * after seeing merge.<name>.var1. + */ + name = var + 6; + namelen = ep - name; + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) + break; + if (!fn) { + fn = xcalloc(1, sizeof(struct ll_merge_driver)); + fn->name = xmemdupz(name, namelen); + fn->fn = ll_ext_merge; + *ll_user_merge_tail = fn; + ll_user_merge_tail = &(fn->next); + } + + ep++; + + if (!strcmp("name", ep)) { + if (!value) + return error("%s: lacks value", var); + fn->description = strdup(value); + return 0; + } + + if (!strcmp("driver", ep)) { + if (!value) + return error("%s: lacks value", var); + /* + * merge.<name>.driver specifies the command line: + * + * command-line + * + * The command-line will be interpolated with the following + * tokens and is given to the shell: + * + * %O - temporary file name for the merge base. + * %A - temporary file name for our version. + * %B - temporary file name for the other branches' version. + * + * The external merge driver should write the results in the + * file named by %A, and signal that it has done with zero exit + * status. + */ + fn->cmdline = strdup(value); + return 0; + } + + if (!strcmp("recursive", ep)) { + if (!value) + return error("%s: lacks value", var); + fn->recursive = strdup(value); + return 0; + } + + return 0; +} + +static void initialize_ll_merge(void) +{ + if (ll_user_merge_tail) + return; + ll_user_merge_tail = &ll_user_merge; + git_config(read_merge_config); +} + +static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) +{ + struct ll_merge_driver *fn; + const char *name; + int i; + + initialize_ll_merge(); + + if (ATTR_TRUE(merge_attr)) + return &ll_merge_drv[LL_TEXT_MERGE]; + else if (ATTR_FALSE(merge_attr)) + return &ll_merge_drv[LL_BINARY_MERGE]; + else if (ATTR_UNSET(merge_attr)) { + if (!default_ll_merge) + return &ll_merge_drv[LL_TEXT_MERGE]; + else + name = default_ll_merge; + } + else + name = merge_attr; + + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strcmp(fn->name, name)) + return fn; + + for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) + if (!strcmp(ll_merge_drv[i].name, name)) + return &ll_merge_drv[i]; + + /* default to the 3-way */ + return &ll_merge_drv[LL_TEXT_MERGE]; +} + +static const char *git_path_check_merge(const char *path) +{ + static struct git_attr_check attr_merge_check; + + if (!attr_merge_check.attr) + attr_merge_check.attr = git_attr("merge", 5); + + if (git_checkattr(path, 1, &attr_merge_check)) + return NULL; + return attr_merge_check.value; +} + +int ll_merge(mmbuffer_t *result_buf, + const char *path, + mmfile_t *ancestor, + mmfile_t *ours, const char *our_label, + mmfile_t *theirs, const char *their_label, + int virtual_ancestor) +{ + const char *ll_driver_name; + const struct ll_merge_driver *driver; + + ll_driver_name = git_path_check_merge(path); + driver = find_ll_merge_driver(ll_driver_name); + + if (virtual_ancestor && driver->recursive) + driver = find_ll_merge_driver(driver->recursive); + return driver->fn(driver, result_buf, path, + ancestor, + ours, our_label, + theirs, their_label, virtual_ancestor); +} diff --git a/ll-merge.h b/ll-merge.h new file mode 100644 index 0000000000..5388422d09 --- /dev/null +++ b/ll-merge.h @@ -0,0 +1,15 @@ +/* + * Low level 3-way in-core file merge. + */ + +#ifndef LL_MERGE_H +#define LL_MERGE_H + +int ll_merge(mmbuffer_t *result_buf, + const char *path, + mmfile_t *ancestor, + mmfile_t *ours, const char *our_label, + mmfile_t *theirs, const char *their_label, + int virtual_ancestor); + +#endif diff --git a/log-tree.c b/log-tree.c index 608f697cf3..5b2963998c 100644 --- a/log-tree.c +++ b/log-tree.c @@ -138,10 +138,14 @@ static int has_non_ascii(const char *s) } void log_write_email_headers(struct rev_info *opt, const char *name, - const char **subject_p, const char **extra_headers_p) + const char **subject_p, + const char **extra_headers_p, + int *need_8bit_cte_p) { const char *subject = NULL; const char *extra_headers = opt->extra_headers; + + *need_8bit_cte_p = 0; /* unknown */ if (opt->total > 0) { static char buffer[64]; snprintf(buffer, sizeof(buffer), @@ -169,6 +173,7 @@ void log_write_email_headers(struct rev_info *opt, const char *name, if (opt->mime_boundary) { static char subject_buffer[1024]; static char buffer[1024]; + *need_8bit_cte_p = -1; /* NEVER */ snprintf(subject_buffer, sizeof(subject_buffer) - 1, "%s" "MIME-Version: 1.0\n" @@ -212,6 +217,7 @@ void show_log(struct rev_info *opt, const char *sep) int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; const char *extra; const char *subject = NULL, *extra_headers = opt->extra_headers; + int need_8bit_cte = 0; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -255,7 +261,8 @@ void show_log(struct rev_info *opt, const char *sep) if (opt->commit_format == CMIT_FMT_EMAIL) { log_write_email_headers(opt, sha1_to_hex(commit->object.sha1), - &subject, &extra_headers); + &subject, &extra_headers, + &need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) @@ -299,9 +306,11 @@ void show_log(struct rev_info *opt, const char *sep) * And then the pretty-printed message itself */ strbuf_init(&msgbuf, 0); + if (need_8bit_cte >= 0) + need_8bit_cte = has_non_ascii(opt->add_signoff); pretty_print_commit(opt->commit_format, commit, &msgbuf, abbrev, subject, extra_headers, opt->date_mode, - has_non_ascii(opt->add_signoff)); + need_8bit_cte); if (opt->add_signoff) append_signoff(&msgbuf, opt->add_signoff); diff --git a/log-tree.h b/log-tree.h index 0cc9344eab..8946ff377c 100644 --- a/log-tree.h +++ b/log-tree.h @@ -14,6 +14,8 @@ 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); void log_write_email_headers(struct rev_info *opt, const char *name, - const char **subject_p, const char **extra_headers_p); + const char **subject_p, + const char **extra_headers_p, + int *need_8bit_cte_p); #endif diff --git a/merge-tree.c b/merge-tree.c index e08324686c..02fc10f7e6 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -168,7 +168,13 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsi return res; } -static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result) +static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) +{ + char *path = xmalloc(traverse_path_len(info, n) + 1); + return make_traverse_path(path, info, n); +} + +static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) { struct merge_list *orig, *final; const char *path; @@ -177,7 +183,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en if (!branch1) return; - path = xstrdup(mkpath("%s%s", base, result->path)); + path = traverse_path(info, result); orig = create_entry(2, branch1->mode, branch1->sha1, path); final = create_entry(0, result->mode, result->sha1, path); @@ -186,9 +192,8 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en add_merge_entry(final); } -static int unresolved_directory(const char *base, struct name_entry n[3]) +static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) { - int baselen, pathlen; char *newbase; struct name_entry *p; struct tree_desc t[3]; @@ -204,13 +209,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3]) } if (!S_ISDIR(p->mode)) return 0; - baselen = strlen(base); - pathlen = tree_entry_len(p->path, p->sha1); - newbase = xmalloc(baselen + pathlen + 2); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, p->path, pathlen); - memcpy(newbase + baselen + pathlen, "/", 2); - + newbase = traverse_path(info, p); buf0 = fill_tree_descriptor(t+0, n[0].sha1); buf1 = fill_tree_descriptor(t+1, n[1].sha1); buf2 = fill_tree_descriptor(t+2, n[2].sha1); @@ -224,7 +223,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3]) } -static struct merge_list *link_entry(unsigned stage, const char *base, struct name_entry *n, struct merge_list *entry) +static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry) { const char *path; struct merge_list *link; @@ -234,17 +233,17 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na if (entry) path = entry->path; else - path = xstrdup(mkpath("%s%s", base, n->path)); + path = traverse_path(info, n); link = create_entry(stage, n->mode, n->sha1, path); link->link = entry; return link; } -static void unresolved(const char *base, struct name_entry n[3]) +static void unresolved(const struct traverse_info *info, struct name_entry n[3]) { struct merge_list *entry = NULL; - if (unresolved_directory(base, n)) + if (unresolved_directory(info, n)) return; /* @@ -252,9 +251,9 @@ static void unresolved(const char *base, struct name_entry n[3]) * list has the stages in order - link_entry adds new * links at the front. */ - entry = link_entry(3, base, n + 2, entry); - entry = link_entry(2, base, n + 1, entry); - entry = link_entry(1, base, n + 0, entry); + entry = link_entry(3, info, n + 2, entry); + entry = link_entry(2, info, n + 1, entry); + entry = link_entry(1, info, n + 0, entry); add_merge_entry(entry); } @@ -288,36 +287,41 @@ static void unresolved(const char *base, struct name_entry n[3]) * The successful merge rules are the same as for the three-way merge * in git-read-tree. */ -static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base) +static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { - resolve(base, NULL, entry+1); - return; + resolve(info, NULL, entry+1); + return mask; } } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { - resolve(base, entry+1, entry+2); - return; + resolve(info, entry+1, entry+2); + return mask; } } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - resolve(base, NULL, entry+1); - return; + resolve(info, NULL, entry+1); + return mask; } } - unresolved(base, entry); + unresolved(info, entry); + return mask; } static void merge_trees(struct tree_desc t[3], const char *base) { - traverse_trees(3, t, base, threeway_callback); + struct traverse_info info; + + setup_traverse_info(&info, base); + info.fn = threeway_callback; + traverse_trees(3, t, &info); } static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) diff --git a/parse-options.c b/parse-options.c index d9562ba504..8e64316fe0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -6,7 +6,8 @@ struct optparse_t { const char **argv; - int argc; + const char **out; + int argc, cpidx; const char *opt; }; @@ -159,6 +160,16 @@ static int parse_long_opt(struct optparse_t *p, const char *arg, continue; rest = skip_prefix(arg, options->long_name); + if (options->type == OPTION_ARGUMENT) { + if (!rest) + continue; + if (*rest == '=') + return opterror(options, "takes no value", flags); + if (*rest) + continue; + p->out[p->cpidx++] = arg - 2; + return 0; + } if (!rest) { /* abbreviated? */ if (!strncmp(options->long_name, arg, arg_end - arg)) { @@ -242,14 +253,15 @@ static NORETURN void usage_with_options_internal(const char * const *, 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; + struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL }; for (; args.argc; args.argc--, args.argv++) { const char *arg = args.argv[0]; if (*arg != '-' || !arg[1]) { - argv[j++] = args.argv[0]; + if (flags & PARSE_OPT_STOP_AT_NON_OPTION) + break; + args.out[args.cpidx++] = args.argv[0]; continue; } @@ -286,9 +298,9 @@ int parse_options(int argc, const char **argv, const struct option *options, usage_with_options(usagestr, options); } - memmove(argv + j, args.argv, args.argc * sizeof(*argv)); - argv[j + args.argc] = NULL; - return j + args.argc; + memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out)); + args.out[args.cpidx + args.argc] = NULL; + return args.cpidx + args.argc; } #define USAGE_OPTS_WIDTH 24 @@ -328,6 +340,8 @@ void usage_with_options_internal(const char * const *usagestr, pos += fprintf(stderr, "--%s", opts->long_name); switch (opts->type) { + case OPTION_ARGUMENT: + break; case OPTION_INTEGER: if (opts->flags & PARSE_OPT_OPTARG) pos += fprintf(stderr, " [<n>]"); diff --git a/parse-options.h b/parse-options.h index 102ac31fb7..1af62b0485 100644 --- a/parse-options.h +++ b/parse-options.h @@ -4,6 +4,7 @@ enum parse_opt_type { /* special types */ OPTION_END, + OPTION_ARGUMENT, OPTION_GROUP, /* options with no arguments */ OPTION_BIT, @@ -18,6 +19,7 @@ enum parse_opt_type { enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1, + PARSE_OPT_STOP_AT_NON_OPTION = 2, }; enum parse_opt_option_flags { @@ -84,6 +86,7 @@ struct option { }; #define OPT_END() { OPTION_END } +#define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, (h) } #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) } diff --git a/path-list.c b/path-list.c index 3d83b7ba9e..92e5cf20fe 100644 --- a/path-list.c +++ b/path-list.c @@ -102,3 +102,33 @@ void print_path_list(const char *text, const struct path_list *p) for (i = 0; i < p->nr; i++) printf("%s:%p\n", p->items[i].path, p->items[i].util); } + +struct path_list_item *path_list_append(const char *path, struct path_list *list) +{ + ALLOC_GROW(list->items, list->nr + 1, list->alloc); + list->items[list->nr].path = + list->strdup_paths ? xstrdup(path) : (char *)path; + return list->items + list->nr++; +} + +static int cmp_items(const void *a, const void *b) +{ + const struct path_list_item *one = a; + const struct path_list_item *two = b; + return strcmp(one->path, two->path); +} + +void sort_path_list(struct path_list *list) +{ + qsort(list->items, list->nr, sizeof(*list->items), cmp_items); +} + +int unsorted_path_list_has_path(struct path_list *list, const char *path) +{ + int i; + for (i = 0; i < list->nr; i++) + if (!strcmp(path, list->items[i].path)) + return 1; + return 0; +} + diff --git a/path-list.h b/path-list.h index 5931e2cc0c..ca2cbbaa4d 100644 --- a/path-list.h +++ b/path-list.h @@ -13,10 +13,16 @@ struct path_list }; void print_path_list(const char *text, const struct path_list *p); +void path_list_clear(struct path_list *list, int free_util); +/* Use these functions only on sorted lists: */ int path_list_has_path(const struct path_list *list, const char *path); -void path_list_clear(struct path_list *list, int free_util); struct path_list_item *path_list_insert(const char *path, struct path_list *list); struct path_list_item *path_list_lookup(const char *path, struct path_list *list); +/* Use these functions only on unsorted lists: */ +struct path_list_item *path_list_append(const char *path, struct path_list *list); +void sort_path_list(struct path_list *list); +int unsorted_path_list_has_path(struct path_list *list, const char *path); + #endif /* PATH_LIST_H */ @@ -283,7 +283,7 @@ int adjust_shared_perm(const char *path) ? (S_IXGRP|S_IXOTH) : 0)); if (S_ISDIR(mode)) - mode |= S_ISGID; + mode |= FORCE_DIR_SET_GID; if ((mode & st.st_mode) != mode && chmod(path, mode) < 0) return -2; return 0; @@ -636,7 +636,7 @@ void pp_title_line(enum cmit_fmt fmt, const char *subject, const char *after_subject, const char *encoding, - int plain_non_ascii) + int need_8bit_cte) { struct strbuf title; @@ -669,7 +669,7 @@ void pp_title_line(enum cmit_fmt fmt, } strbuf_addch(sb, '\n'); - if (plain_non_ascii) { + if (need_8bit_cte > 0) { const char *header_fmt = "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=%s\n" @@ -718,9 +718,9 @@ void pp_remainder(enum cmit_fmt fmt, } 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) + struct strbuf *sb, int abbrev, + const char *subject, const char *after_subject, + enum date_mode dmode, int need_8bit_cte) { unsigned long beginning_of_body; int indent = 4; @@ -746,13 +746,11 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, 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. + /* + * We need to check and emit Content-type: to mark it + * as 8-bit if we haven't done so. */ - if (fmt == CMIT_FMT_EMAIL && !after_subject) { + if (fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) { int i, ch, in_body; for (in_body = i = 0; (ch = msg[i]); i++) { @@ -765,7 +763,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, in_body = 1; } else if (non_ascii(ch)) { - plain_non_ascii = 1; + need_8bit_cte = 1; break; } } @@ -790,7 +788,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, /* 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); + after_subject, encoding, need_8bit_cte); beginning_of_body = sb->len; if (fmt != CMIT_FMT_ONELINE) @@ -260,6 +260,48 @@ extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, fputc(terminator, fp); } +/* quote path as relative to the given prefix */ +char *quote_path_relative(const char *in, int len, + struct strbuf *out, const char *prefix) +{ + int needquote; + + if (len < 0) + len = strlen(in); + + /* "../" prefix itself does not need quoting, but "in" might. */ + needquote = next_quote_pos(in, len) < len; + strbuf_setlen(out, 0); + strbuf_grow(out, len); + + if (needquote) + strbuf_addch(out, '"'); + if (prefix) { + int off = 0; + while (prefix[off] && off < len && prefix[off] == in[off]) + if (prefix[off] == '/') { + prefix += off + 1; + in += off + 1; + len -= off + 1; + off = 0; + } else + off++; + + for (; *prefix; prefix++) + if (*prefix == '/') + strbuf_addstr(out, "../"); + } + + quote_c_style_counted (in, len, out, NULL, 1); + + if (needquote) + strbuf_addch(out, '"'); + if (!out->len) + strbuf_addstr(out, "./"); + + return out->buf; +} + /* * C-style name unquoting. * @@ -288,7 +330,7 @@ int unquote_c_style(struct strbuf *sb, const char *quoted, const char **endp) switch (*quoted++) { case '"': if (endp) - *endp = quoted + 1; + *endp = quoted; return 0; case '\\': break; @@ -47,6 +47,10 @@ 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); +/* quote path as relative to the given prefix */ +char *quote_path_relative(const char *in, int len, + struct strbuf *out, const char *prefix); + /* quoting as a string literal for other languages */ extern void perl_quote_print(FILE *stream, const char *src); extern void python_quote_print(FILE *stream, const char *src); diff --git a/read-cache.c b/read-cache.c index 657f0c5894..a92b25b59b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -255,13 +255,13 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce) +static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce) { return (istate->timestamp && ((unsigned int)istate->timestamp) <= ce->ce_mtime); } -int ie_match_stat(struct index_state *istate, +int ie_match_stat(const struct index_state *istate, struct cache_entry *ce, struct stat *st, unsigned int options) { @@ -304,7 +304,7 @@ int ie_match_stat(struct index_state *istate, return changed; } -int ie_modified(struct index_state *istate, +int ie_modified(const struct index_state *istate, struct cache_entry *ce, struct stat *st, unsigned int options) { int changed, changed_fs; @@ -351,6 +351,41 @@ int base_name_compare(const char *name1, int len1, int mode1, return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +/* + * df_name_compare() is identical to base_name_compare(), except it + * compares conflicting directory/file entries as equal. Note that + * while a directory name compares as equal to a regular file, they + * then individually compare _differently_ to a filename that has + * a dot after the basename (because '\0' < '.' < '/'). + * + * This is used by routines that want to traverse the git namespace + * but then handle conflicting entries together when possible. + */ +int df_name_compare(const char *name1, int len1, int mode1, + const char *name2, int len2, int mode2) +{ + int len = len1 < len2 ? len1 : len2, cmp; + unsigned char c1, c2; + + cmp = memcmp(name1, name2, len); + if (cmp) + return cmp; + /* Directories and files compare equal (same length, same name) */ + if (len1 == len2) + return 0; + c1 = name1[len]; + if (!c1 && S_ISDIR(mode1)) + c1 = '/'; + c2 = name2[len]; + if (!c2 && S_ISDIR(mode2)) + c2 = '/'; + if (c1 == '/' && !c2) + return 0; + if (c2 == '/' && !c1) + return 0; + return c1 - c2; +} + int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2) { int len1 = flags1 & CE_NAMEMASK; @@ -377,7 +412,7 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla return 0; } -int index_name_pos(struct index_state *istate, const char *name, int namelen) +int index_name_pos(const struct index_state *istate, const char *name, int namelen) { int first, last; @@ -1166,7 +1201,7 @@ int discard_index(struct index_state *istate) return 0; } -int unmerged_index(struct index_state *istate) +int unmerged_index(const struct index_state *istate) { int i; for (i = 0; i < istate->cache_nr; i++) { @@ -1311,7 +1346,7 @@ static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce) return ce_write(c, fd, ondisk, size); } -int write_index(struct index_state *istate, int newfd) +int write_index(const struct index_state *istate, int newfd) { SHA_CTX c; struct cache_header hdr; diff --git a/receive-pack.c b/receive-pack.c index de7e18c49e..f83ae87e15 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -10,7 +10,6 @@ static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static int deny_non_fast_forwards = 0; -static int receive_fsck_objects = 1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; @@ -36,11 +35,6 @@ static int receive_pack_config(const char *var, const char *value) return 0; } - if (strcmp(var, "receive.fsckobjects") == 0) { - receive_fsck_objects = git_config_bool(var, value); - return 0; - } - return git_default_config(var, value); } @@ -374,13 +368,11 @@ static const char *unpack(void) ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries)); if (ntohl(hdr.hdr_entries) < unpack_limit) { - int code, i = 0; - const char *unpacker[4]; - unpacker[i++] = "unpack-objects"; - if (receive_fsck_objects) - unpacker[i++] = "--strict"; - unpacker[i++] = hdr_arg; - unpacker[i++] = NULL; + int code; + const char *unpacker[3]; + unpacker[0] = "unpack-objects"; + unpacker[1] = hdr_arg; + unpacker[2] = NULL; code = run_command_v_opt(unpacker, RUN_GIT_CMD); switch (code) { case 0: @@ -401,8 +393,8 @@ static const char *unpack(void) return "unpacker exited with error code"; } } else { - const char *keeper[7]; - int s, status, i = 0; + const char *keeper[6]; + int s, status; char keep_arg[256]; struct child_process ip; @@ -410,14 +402,12 @@ static const char *unpack(void) if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) strcpy(keep_arg + s, "localhost"); - keeper[i++] = "index-pack"; - keeper[i++] = "--stdin"; - if (receive_fsck_objects) - keeper[i++] = "--strict"; - keeper[i++] = "--fix-thin"; - keeper[i++] = hdr_arg; - keeper[i++] = keep_arg; - keeper[i++] = NULL; + keeper[0] = "index-pack"; + keeper[1] = "--stdin"; + keeper[2] = "--fix-thin"; + keeper[3] = hdr_arg; + keeper[4] = keep_arg; + keeper[5] = NULL; memset(&ip, 0, sizeof(ip)); ip.argv = keeper; ip.out = -1; @@ -1033,7 +1033,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) return 1; } -static int close_ref(struct ref_lock *lock) +int close_ref(struct ref_lock *lock) { if (close_lock_file(lock->lk)) return -1; @@ -1041,7 +1041,7 @@ static int close_ref(struct ref_lock *lock) return 0; } -static int commit_ref(struct ref_lock *lock) +int commit_ref(struct ref_lock *lock) { if (commit_lock_file(lock->lk)) return -1; @@ -33,6 +33,12 @@ extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_ #define REF_NODEREF 0x01 extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags); +/** Close the file descriptor owned by a lock and return the status */ +extern int close_ref(struct ref_lock *lock); + +/** Close and commit the ref locked by the lock */ +extern int commit_ref(struct ref_lock *lock); + /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); @@ -357,7 +357,8 @@ static int handle_config(const char *key, const char *value) remote->fetch_tags = -1; } else if (!strcmp(subkey, ".proxy")) { remote->http_proxy = xstrdup(value); - } + } else if (!strcmp(subkey, ".skipdefaultupdate")) + remote->skip_default_update = 1; return 0; } @@ -25,6 +25,7 @@ struct remote { * 2 to always fetch tags */ int fetch_tags; + int skip_default_update; const char *receivepack; const char *uploadpack; diff --git a/run-command.c b/run-command.c index 743757c36e..44100a749b 100644 --- a/run-command.c +++ b/run-command.c @@ -91,6 +91,13 @@ int start_command(struct child_process *cmd) close(cmd->in); } + if (cmd->no_stderr) + dup_devnull(2); + else if (need_err) { + dup2(fderr[1], 2); + close_pair(fderr); + } + if (cmd->no_stdout) dup_devnull(1); else if (cmd->stdout_to_stderr) @@ -103,13 +110,6 @@ 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)); @@ -202,6 +202,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec) const char *p = prefix_path(prefix, prefixlen, *src); if (p) *(dst++) = p; + else + exit(128); /* error message already given */ src++; } *dst = NULL; diff --git a/sha1_name.c b/sha1_name.c index 8358ba2069..491d2e7ebf 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -407,21 +407,56 @@ static int get_nth_ancestor(const char *name, int len, unsigned char *result, int generation) { unsigned char sha1[20]; - int ret = get_sha1_1(name, len, sha1); + struct commit *commit; + int ret; + + ret = get_sha1_1(name, len, sha1); if (ret) return ret; + commit = lookup_commit_reference(sha1); + if (!commit) + return -1; while (generation--) { - struct commit *commit = lookup_commit_reference(sha1); - - if (!commit || parse_commit(commit) || !commit->parents) + if (parse_commit(commit) || !commit->parents) return -1; - hashcpy(sha1, commit->parents->item->object.sha1); + commit = commit->parents->item; } - hashcpy(result, sha1); + hashcpy(result, commit->object.sha1); return 0; } +struct object *peel_to_type(const char *name, int namelen, + struct object *o, enum object_type expected_type) +{ + if (name && !namelen) + namelen = strlen(name); + if (!o) { + unsigned char sha1[20]; + if (get_sha1_1(name, namelen, sha1)) + return NULL; + o = parse_object(sha1); + } + while (1) { + if (!o || (!o->parsed && !parse_object(o->sha1))) + return NULL; + if (o->type == expected_type) + return o; + if (o->type == OBJ_TAG) + o = ((struct tag*) o)->tagged; + else if (o->type == OBJ_COMMIT) + o = &(((struct commit *) o)->tree->object); + else { + if (name) + error("%.*s: expected %s type, but the object " + "dereferences to %s type", + namelen, name, typename(expected_type), + typename(o->type)); + return NULL; + } + } +} + static int peel_onion(const char *name, int len, unsigned char *sha1) { unsigned char outer[20]; @@ -473,32 +508,17 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) hashcpy(sha1, o->sha1); } else { - /* At this point, the syntax look correct, so + /* + * At this point, the syntax look correct, so * if we do not get the needed object, we should * barf. */ - - while (1) { - if (!o || (!o->parsed && !parse_object(o->sha1))) - return -1; - if (o->type == expected_type) { - hashcpy(sha1, o->sha1); - return 0; - } - if (o->type == OBJ_TAG) - o = ((struct tag*) o)->tagged; - else if (o->type == OBJ_COMMIT) - o = &(((struct commit *) o)->tree->object); - else - return error("%.*s: expected %s type, but the object dereferences to %s type", - len, name, typename(expected_type), - typename(o->type)); - if (!o) - return -1; - if (!o->parsed) - if (!parse_object(o->sha1)) - return -1; + o = peel_to_type(name, len, o, expected_type); + if (o) { + hashcpy(sha1, o->sha1); + return 0; } + return -1; } return 0; } @@ -528,9 +548,8 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) int ret, has_suffix; const char *cp; - /* "name~3" is "name^^^", - * "name~" and "name~0" are name -- not "name^0"! - * "name^" is not "name^0"; it is "name^1". + /* + * "name~3" is "name^^^", "name~" is "name~1", and "name^" is "name^1". */ has_suffix = 0; for (cp = name + len - 1; name <= cp; cp--) { @@ -548,11 +567,10 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) cp++; while (cp < name + len) num = num * 10 + *cp++ - '0'; - if (has_suffix == '^') { - if (!num && len1 == len - 1) - num = 1; + if (!num && len1 == len - 1) + num = 1; + if (has_suffix == '^') return get_parent(name, len1, sha1, num); - } /* else if (has_suffix == '~') -- goes without saying */ return get_nth_ancestor(name, len1, sha1, num); } diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index cb860296ed..8fc39d77ce 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -5,7 +5,9 @@ test_description='blob conversion via gitattributes' . ./test-lib.sh cat <<\EOF >rot13.sh -tr '[a-zA-Z]' '[n-za-mN-ZA-M]' +tr \ + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' EOF chmod +x rot13.sh diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 0e2933a984..c23f0ace85 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -21,6 +21,9 @@ string options --st <st> get another string (pervert ordering) -o <str> get another string +magic arguments + --quux means --quux + EOF test_expect_success 'test help' ' @@ -114,4 +117,17 @@ test_expect_success 'detect possible typos' ' git diff expect.err output.err ' +cat > expect <<EOF +boolean: 0 +integer: 0 +string: (not set) +arg 00: --quux +EOF + +test_expect_success 'keep some options as arguments' ' + test-parse-options --quux > output 2> output.err && + test ! -s output.err && + git diff expect output +' + test_done diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh new file mode 100755 index 0000000000..8c4556408e --- /dev/null +++ b/t/t1005-read-tree-reset.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='read-tree -u --reset' + +. ./test-lib.sh + +# two-tree test + +test_expect_success 'setup' ' + git init && + mkdir df && + echo content >df/file && + git add df/file && + git commit -m one && + git ls-files >expect && + rm -rf df && + echo content >df && + git add df && + echo content >new && + git add new && + git commit -m two +' + +test_expect_success 'reset should work' ' + git read-tree -u --reset HEAD^ && + git ls-files >actual && + diff -u expect actual +' + +test_done diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index f959aae846..73f830db23 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -175,21 +175,30 @@ test_expect_success 'recover and check' ' ' -test_expect_success 'prune --expire' ' - - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - git reset --hard && - git prune --expire=1.hour.ago && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - test-chmtime -86500 $BLOB_FILE && - git prune --expire 1.day && - test $before = $(git count-objects | sed "s/ .*//") && - ! test -f $BLOB_FILE +test_expect_success 'delete' ' + echo 1 > C && + test_tick && + git commit -m rat C && + + echo 2 > C && + test_tick && + git commit -m ox C && + + echo 3 > C && + test_tick && + git commit -m tiger C && + + test 5 = $(git reflog | wc -l) && + + git reflog delete master@{1} && + git reflog show master > output && + test 4 = $(wc -l < output) && + ! grep ox < output && + + git reflog delete master@{07.04.2005.15:15:00.-0700} && + git reflog show master > output && + test 3 = $(wc -l < output) && + ! grep dragon < output ' diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 39fe2676dc..70f9ce9d52 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -120,7 +120,7 @@ EOF # having 1.txt and path3 test_expect_success \ 'ls-tree filter odd names' \ - 'git ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current && + 'git ls-tree $tree 1.txt ./1.txt .//1.txt path3/1.txt path3/./1.txt path3 path3// >current && cat >expected <<\EOF && 100644 blob X 1.txt 100644 blob X path3/1.txt diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index 3417138a80..37944c39a3 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -23,37 +23,49 @@ test_expect_success setup ' git branch pre-rebase ' -test_expect_success 'rebase --abort' ' - test_must_fail git rebase master && - git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) -' +testrebase() { + type=$1 + dotest=$2 -test_expect_success 'rebase --abort after --skip' ' - # Clean up the state from the previous one - git reset --hard pre-rebase - rm -rf .dotest + test_expect_success "rebase$type --abort" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + git rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' - test_must_fail git rebase master && - test_must_fail git rebase --skip && - test $(git rev-parse HEAD) = $(git rev-parse master) && - git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) -' + test_expect_success "rebase$type --abort after --skip" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + test_must_fail git rebase --skip && + test $(git rev-parse HEAD) = $(git rev-parse master) && + git-rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' -test_expect_success 'rebase --abort after --continue' ' - # Clean up the state from the previous one - git reset --hard pre-rebase - rm -rf .dotest + test_expect_success "rebase$type --abort after --continue" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + echo c > a && + echo d >> a && + git add a && + test_must_fail git rebase --continue && + test $(git rev-parse HEAD) != $(git rev-parse master) && + git rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' +} - test_must_fail git rebase master && - echo c > a && - echo d >> a && - git add a && - test_must_fail git rebase --continue && - test $(git rev-parse HEAD) != $(git rev-parse master) && - git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) -' +testrebase "" .dotest +testrebase " --merge" .git/.dotest-merge test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 9a9a250d2c..aa282e1bc1 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -40,8 +40,8 @@ test_expect_success 'parents of stash' ' test_expect_success 'apply needs clean working directory' ' echo 4 > other-file && git add other-file && - echo 5 > other-file - ! git stash apply + echo 5 > other-file && + test_must_fail git stash apply ' test_expect_success 'apply stashed changes' ' @@ -70,7 +70,51 @@ test_expect_success 'unstashing in a subdirectory' ' git reset --hard HEAD && mkdir subdir && cd subdir && - git stash apply + git stash apply && + cd .. +' + +test_expect_success 'drop top stash' ' + git reset --hard && + git stash list > stashlist1 && + echo 7 > file && + git stash && + git stash drop && + git stash list > stashlist2 && + diff stashlist1 stashlist2 && + git stash apply && + test 3 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) +' + +test_expect_success 'drop middle stash' ' + git reset --hard && + echo 8 > file && + git stash && + echo 9 > file && + git stash && + git stash drop stash@{1} && + test 2 = $(git stash list | wc -l) && + git stash apply && + test 9 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) && + git reset --hard && + git stash drop && + git stash apply && + test 3 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) +' + +test_expect_success 'stash pop' ' + git reset --hard && + git stash pop && + test 3 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) && + test 0 = $(git stash list | wc -l) ' test_done diff --git a/t/t4021-format-patch-signer-mime.sh b/t/t4021-format-patch-signer-mime.sh index 67a70fadab..ba43f18549 100755 --- a/t/t4021-format-patch-signer-mime.sh +++ b/t/t4021-format-patch-signer-mime.sh @@ -32,11 +32,19 @@ test_expect_success 'format with signoff without funny signer name' ' test_expect_success 'format with non ASCII signer name' ' - GIT_COMMITTER_NAME="$B$O$^$N(B $B$U$K$*$&(B" \ + GIT_COMMITTER_NAME="はまの ふにおう" \ git format-patch -s --stdout -1 >output && grep Content-Type output ' +test_expect_success 'attach and signoff do not duplicate mime headers' ' + + GIT_COMMITTER_NAME="はまの ふにおう" \ + git format-patch -s --stdout -1 --attach >output && + test `grep -ci ^MIME-Version: output` = 1 + +' + test_done diff --git a/t/t4028-format-patch-mime-headers.sh b/t/t4028-format-patch-mime-headers.sh new file mode 100755 index 0000000000..204ba673cb --- /dev/null +++ b/t/t4028-format-patch-mime-headers.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='format-patch mime headers and extra headers do not conflict' +. ./test-lib.sh + +test_expect_success 'create commit with utf-8 body' ' + echo content >file && + git add file && + git commit -m one && + echo more >>file && + git commit -a -m "two + + utf-8 body: ñ" +' + +test_expect_success 'patch has mime headers' ' + rm -f 0001-two.patch && + git format-patch HEAD^ && + grep -i "content-type: text/plain; charset=utf-8" 0001-two.patch +' + +test_expect_success 'patch has mime and extra headers' ' + rm -f 0001-two.patch && + git config format.headers "x-foo: bar" && + git format-patch HEAD^ && + grep -i "x-foo: bar" 0001-two.patch && + grep -i "content-type: text/plain; charset=utf-8" 0001-two.patch +' + +test_done diff --git a/t/t4150-am-subdir.sh b/t/t4150-am-subdir.sh new file mode 100755 index 0000000000..929d2cbd87 --- /dev/null +++ b/t/t4150-am-subdir.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +test_description='git am running from a subdirectory' + +. ./test-lib.sh + +test_expect_success setup ' + echo hello >world && + git add world && + test_tick && + git commit -m initial && + git tag initial && + echo goodbye >world && + git add world && + test_tick && + git commit -m second && + git format-patch --stdout HEAD^ >patchfile && + : >expect +' + +test_expect_success 'am regularly from stdin' ' + git checkout initial && + git am <patchfile && + git diff master >actual && + diff -u expect actual +' + +test_expect_success 'am regularly from file' ' + git checkout initial && + git am patchfile && + git diff master >actual && + diff -u expect actual +' + +test_expect_success 'am regularly from stdin in subdirectory' ' + rm -fr subdir && + git checkout initial && + ( + mkdir -p subdir && + cd subdir && + git am <../patchfile + ) && + git diff master>actual && + diff -u expect actual +' + +test_expect_success 'am regularly from file in subdirectory' ' + rm -fr subdir && + git checkout initial && + ( + mkdir -p subdir && + cd subdir && + git am ../patchfile + ) && + git diff master >actual && + diff -u expect actual +' + +test_expect_success 'am regularly from file in subdirectory with full path' ' + rm -fr subdir && + git checkout initial && + P=$(pwd) && + ( + mkdir -p subdir && + cd subdir && + git am "$P/patchfile" + ) && + git diff master >actual && + diff -u expect actual +' + +test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 6d12efb74d..eef4cafda9 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -47,4 +47,9 @@ EOF test_expect_success 'shortlog wrapping' 'diff -u expect out' +git log HEAD > log +GIT_DIR=non-existing git shortlog -w < log > out + +test_expect_success 'shortlog from non-git directory' 'diff -u expect out' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 6560af756e..47090c4cf5 100644 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -29,4 +29,53 @@ test_expect_success 'prune stale packs' ' ' +test_expect_success 'prune --expire' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + git prune --expire=1.hour.ago && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -86500 $BLOB_FILE && + git prune --expire 1.day && + test $before = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + +' + +test_expect_success 'gc: implicit prune --expire' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -$((86400*14-30)) $BLOB_FILE && + git gc && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -$((86400*14+1)) $BLOB_FILE && + git gc && + test $before = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + +' + +test_expect_success 'gc: refuse to start with invalid gc.pruneExpire' ' + + git config gc.pruneExpire invalid && + test_must_fail git gc + +' + +test_expect_success 'gc: start with ok gc.pruneExpire' ' + + git config gc.pruneExpire 2.days.ago && + git gc + +' + test_done diff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh new file mode 100755 index 0000000000..0db27547ac --- /dev/null +++ b/t/t5305-include-tag.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +test_description='git-pack-object --include-tag' +. ./test-lib.sh + +TRASH=`pwd` + +test_expect_success setup ' + echo c >d && + git update-index --add d && + tree=`git write-tree` && + commit=`git commit-tree $tree </dev/null` && + echo "object $commit" >sig && + echo "type commit" >>sig && + echo "tag mytag" >>sig && + echo "tagger $(git var GIT_COMMITTER_IDENT)" >>sig && + echo >>sig && + echo "our test tag" >>sig && + tag=`git mktag <sig` && + rm d sig && + git update-ref refs/tags/mytag $tag && { + echo $tree && + echo $commit && + git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" + } >obj-list +' + +rm -rf clone.git +test_expect_success 'pack without --include-tag' ' + packname_1=$(git pack-objects \ + --window=0 \ + test-1 <obj-list) +' + +test_expect_success 'unpack objects' ' + ( + GIT_DIR=clone.git && + export GIT_DIR && + git init && + git unpack-objects -n <test-1-${packname_1}.pack && + git unpack-objects <test-1-${packname_1}.pack + ) +' + +test_expect_success 'check unpacked result (have commit, no tag)' ' + git rev-list --objects $commit >list.expect && + ( + GIT_DIR=clone.git && + export GIT_DIR && + test_must_fail git cat-file -e $tag && + git rev-list --objects $commit + ) >list.actual && + git diff list.expect list.actual +' + +rm -rf clone.git +test_expect_success 'pack with --include-tag' ' + packname_1=$(git pack-objects \ + --window=0 \ + --include-tag \ + test-2 <obj-list) +' + +test_expect_success 'unpack objects' ' + ( + GIT_DIR=clone.git && + export GIT_DIR && + git init && + git unpack-objects -n <test-2-${packname_1}.pack && + git unpack-objects <test-2-${packname_1}.pack + ) +' + +test_expect_success 'check unpacked result (have commit, have tag)' ' + git rev-list --objects mytag >list.expect && + ( + GIT_DIR=clone.git && + export GIT_DIR && + git rev-list --objects $tag + ) >list.actual && + git diff list.expect list.actual +' + +test_done diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh new file mode 100755 index 0000000000..86e5b9bc26 --- /dev/null +++ b/t/t5503-tagfollow.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +test_description='test automatic tag following' + +. ./test-lib.sh + +# End state of the repository: +# +# T - tag1 S - tag2 +# / / +# L - A ------ O ------ B +# \ \ \ +# \ C - origin/cat \ +# origin/master master + +test_expect_success setup ' + test_tick && + echo ichi >file && + git add file && + git commit -m L && + L=$(git rev-parse --verify HEAD) && + + ( + mkdir cloned && + cd cloned && + git init-db && + git remote add -f origin .. + ) && + + test_tick && + echo A >file && + git add file && + git commit -m A && + A=$(git rev-parse --verify HEAD) +' + +U=UPLOAD_LOG + +cat - <<EOF >expect +#S +want $A +#E +EOF +test_expect_success 'fetch A (new commit : 1 connection)' ' + rm -f $U + ( + cd cloned && + GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + test $A = $(git rev-parse --verify origin/master) + ) && + test -s $U && + cut -d" " -f1,2 $U >actual && + git diff expect actual +' + +test_expect_success "create tag T on A, create C on branch cat" ' + git tag -a -m tag1 tag1 $A && + T=$(git rev-parse --verify tag1) && + + git checkout -b cat && + echo C >file && + git add file && + git commit -m C && + C=$(git rev-parse --verify HEAD) && + git checkout master +' + +cat - <<EOF >expect +#S +want $C +want $T +#E +EOF +test_expect_success 'fetch C, T (new branch, tag : 1 connection)' ' + rm -f $U + ( + cd cloned && + GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + test $C = $(git rev-parse --verify origin/cat) && + test $T = $(git rev-parse --verify tag1) && + test $A = $(git rev-parse --verify tag1^0) + ) && + test -s $U && + cut -d" " -f1,2 $U >actual && + git diff expect actual +' + +test_expect_success "create commits O, B, tag S on B" ' + test_tick && + echo O >file && + git add file && + git commit -m O && + + test_tick && + echo B >file && + git add file && + git commit -m B && + B=$(git rev-parse --verify HEAD) && + + git tag -a -m tag2 tag2 $B && + S=$(git rev-parse --verify tag2) +' + +cat - <<EOF >expect +#S +want $B +want $S +#E +EOF +test_expect_success 'fetch B, S (commit and tag : 1 connection)' ' + rm -f $U + ( + cd cloned && + GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + test $B = $(git rev-parse --verify origin/master) && + test $B = $(git rev-parse --verify tag2^0) && + test $S = $(git rev-parse --verify tag2) + ) && + test -s $U && + cut -d" " -f1,2 $U >actual && + git diff expect actual +' + +cat - <<EOF >expect +#S +want $B +want $S +#E +EOF +test_expect_success 'new clone fetch master and tags' ' + git branch -D cat + rm -f $U + ( + mkdir clone2 && + cd clone2 && + git init && + git remote add origin .. && + GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + test $B = $(git rev-parse --verify origin/master) && + test $S = $(git rev-parse --verify tag2) && + test $B = $(git rev-parse --verify tag2^0) && + test $T = $(git rev-parse --verify tag1) && + test $A = $(git rev-parse --verify tag1^0) + ) && + test -s $U && + cut -d" " -f1,2 $U >actual && + git diff expect actual +' + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 4fc62f550c..2822a651b5 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -10,10 +10,12 @@ setup_repository () { git init && >file && git add file && + test_tick && git commit -m "Initial" && git checkout -b side && >elif && git add elif && + test_tick && git commit -m "Second" && git checkout master ) @@ -78,6 +80,7 @@ test_expect_success 'add another remote' ' test_expect_success 'remove remote' ' ( cd test && + git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master && git remote rm second ) ' @@ -94,4 +97,144 @@ test_expect_success 'remove remote' ' ) ' +cat > test/expect << EOF +* remote origin + URL: $(pwd)/one/.git + Remote branch merged with 'git pull' while on branch master + master + New remote branch (next fetch will store in remotes/origin) + master + Tracked remote branches + side master +EOF + +test_expect_success 'show' ' + (cd test && + git config --add remote.origin.fetch \ + refs/heads/master:refs/heads/upstream && + git fetch && + git branch -d -r origin/master && + (cd ../one && + echo 1 > file && + test_tick && + git commit -m update file) && + git remote show origin > output && + git diff expect output) +' + +test_expect_success 'prune' ' + (cd one && + git branch -m side side2) && + (cd test && + git fetch origin && + git remote prune origin && + git rev-parse refs/remotes/origin/side2 && + ! git rev-parse refs/remotes/origin/side) +' + +test_expect_success 'add --mirror && prune' ' + (mkdir mirror && + cd mirror && + git init && + git remote add --mirror -f origin ../one) && + (cd one && + git branch -m side2 side) && + (cd mirror && + git rev-parse --verify refs/heads/side2 && + ! git rev-parse --verify refs/heads/side && + git fetch origin && + git remote prune origin && + ! git rev-parse --verify refs/heads/side2 && + git rev-parse --verify refs/heads/side) +' + +cat > one/expect << EOF + apis/master + apis/side + drosophila/another + drosophila/master + drosophila/side +EOF + +test_expect_success 'update' ' + + (cd one && + git remote add drosophila ../two && + git remote add apis ../mirror && + git remote update && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + drosophila/another + drosophila/master + drosophila/side + manduca/master + manduca/side + megaloprepus/master + megaloprepus/side +EOF + +test_expect_success 'update with arguments' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git remote add manduca ../mirror && + git remote add megaloprepus ../mirror && + git config remotes.phobaeticus "drosophila megaloprepus" && + git config remotes.titanus manduca && + git remote update phobaeticus titanus && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + apis/master + apis/side + manduca/master + manduca/side + megaloprepus/master + megaloprepus/side +EOF + +test_expect_success 'update default' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git config remote.drosophila.skipDefaultUpdate true && + git remote update default && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + drosophila/another + drosophila/master + drosophila/side +EOF + +test_expect_success 'update default (overridden, with funny whitespace)' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git config remotes.default "$(printf "\t drosophila \n")" && + git remote update default && + git branch -r > output && + git diff expect output) + +' + test_done diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh new file mode 100755 index 0000000000..5bb6b93780 --- /dev/null +++ b/t/t6031-merge-recursive.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='merge-recursive: handle file mode' +. ./test-lib.sh + +test_expect_success 'mode change in one branch: keep changed version' ' + : >file1 && + git add file1 && + git commit -m initial && + git checkout -b a1 master && + : >dummy && + git add dummy && + git commit -m a && + git checkout -b b1 master && + chmod +x file1 && + git add file1 && + git commit -m b1 && + git checkout a1 && + git merge-recursive master -- a1 b1 && + test -x file1 +' + +test_expect_success 'mode change in both branches: expect conflict' ' + git reset --hard HEAD && + git checkout -b a2 master && + : >file2 && + H=$(git hash-object file2) && + chmod +x file2 && + git add file2 && + git commit -m a2 && + git checkout -b b2 master && + : >file2 && + git add file2 && + git commit -m b2 && + git checkout a2 && + ( + git merge-recursive master -- a2 b2 + test $? = 1 + ) && + git ls-files -u >actual && + ( + echo "100755 $H 2 file2" + echo "100644 $H 3 file2" + ) >expect && + diff -u actual expect && + test -x file2 +' + +test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index a7557bdc79..56bbd8519d 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -15,8 +15,11 @@ test_description='test describe check_describe () { expect="$1" shift - R=$(git describe "$@") && + R=$(git describe "$@" 2>err.actual) + S=$? + cat err.actual >&3 test_expect_success "describe $*" ' + test $S = 0 && case "$R" in $expect) echo happy ;; *) echo "Oops - $R is not $expect"; @@ -95,5 +98,23 @@ check_describe A-* --tags HEAD^^2 check_describe B --tags HEAD^^2^ check_describe B-0-* --long HEAD^^2^ +check_describe A-3-* --long HEAD^^2 + +test_expect_success 'rename tag A to Q locally' ' + mv .git/refs/tags/A .git/refs/tags/Q +' +cat - >err.expect <<EOF +warning: tag 'A' is really 'Q' here +EOF +check_describe A-* HEAD +test_expect_success 'warning was displayed for Q' ' + git diff err.expect err.actual +' +test_expect_success 'rename tag Q back to A' ' + mv .git/refs/tags/Q .git/refs/tags/A +' + +test_expect_success 'pack tag refs' 'git pack-refs' +check_describe A-* HEAD test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 868babc4b2..6e14bf1c7f 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -179,4 +179,28 @@ test_expect_success 'Name needing quotes' ' ' +test_expect_success 'Subdirectory filter with disappearing trees' ' + git reset --hard && + git checkout master && + + mkdir foo && + touch foo/bar && + git add foo && + test_tick && + git commit -m "Adding foo" && + + git rm -r foo && + test_tick && + git commit -m "Removing foo" && + + mkdir foo && + touch foo/bar && + git add foo && + test_tick && + git commit -m "Re-adding foo" && + + git filter-branch -f --subdirectory-filter foo && + test $(git rev-list master | wc -l) = 3 +' + test_done diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index c1cec55306..6a74b3acfd 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -89,6 +89,33 @@ do ' done +test_expect_success 'editor with a space' ' + + if echo "echo space > \"\$1\"" > "e space.sh" + then + chmod a+x "e space.sh" && + GIT_EDITOR="./e\ space.sh" git commit --amend && + test space = "$(git show -s --pretty=format:%s)" + else + say "Skipping; FS does not support spaces in filenames" + fi + +' + +unset GIT_EDITOR +test_expect_success 'core.editor with a space' ' + + if test -f "e space.sh" + then + git config core.editor \"./e\ space.sh\" && + git commit --amend && + test space = "$(git show -s --pretty=format:%s)" + else + say "Skipping; FS does not support spaces in filenames" + fi + +' + TERM="$OLD_TERM" test_done diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh index e809e0e2c9..bc8ab6a619 100755 --- a/t/t7010-setup.sh +++ b/t/t7010-setup.sh @@ -142,15 +142,16 @@ test_expect_success 'setup deeper work tree' ' test_expect_success 'add a directory outside the work tree' '( cd tester && d1="$(cd .. ; pwd)" && - git add "$d1" + test_must_fail git add "$d1" )' + test_expect_success 'add a file outside the work tree, nasty case 1' '( cd tester && f="$(pwd)x" && echo "$f" && touch "$f" && - git add "$f" + test_must_fail git add "$f" )' test_expect_success 'add a file outside the work tree, nasty case 2' '( @@ -158,7 +159,7 @@ test_expect_success 'add a file outside the work tree, nasty case 2' '( f="$(pwd | sed "s/.$//")x" && echo "$f" && touch "$f" && - git add "$f" + test_must_fail git add "$f" )' test_done diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 38403643a6..afccfc9973 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -89,6 +89,58 @@ test_expect_success 'git-clean with prefix' ' test -f build/lib.so ' + +test_expect_success 'git-clean with relative prefix' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + would_clean=$( + cd docs && + git clean -n ../src | + sed -n -e "s|^Would remove ||p" + ) && + test "$would_clean" = ../src/part3.c || { + echo "OOps <$would_clean>" + false + } +' + +test_expect_success 'git-clean with absolute path' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + would_clean=$( + cd docs && + git clean -n $(pwd)/../src | + sed -n -e "s|^Would remove ||p" + ) && + test "$would_clean" = ../src/part3.c || { + echo "OOps <$would_clean>" + false + } +' + +test_expect_success 'git-clean with out of work tree relative path' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + ( + cd docs && + test_must_fail git clean -n ../.. + ) +' + +test_expect_success 'git-clean with out of work tree absolute path' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + dd=$(cd .. && pwd) && + ( + cd docs && + test_must_fail git clean -n $dd + ) +' + test_expect_success 'git-clean -d with prefix and path' ' mkdir -p build docs src/feature && @@ -320,8 +372,9 @@ test_expect_success 'removal failure' ' mkdir foo && touch foo/bar && + exec <foo/bar && chmod 0 foo && - ! git clean -f -d + test_must_fail git clean -f -d ' chmod 755 foo diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 50c51c82fa..5d166280cb 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -419,6 +419,7 @@ test_debug 'gitk --all' test_expect_success 'merge c0 with c1 (no-ff)' ' git reset --hard c0 && + git config branch.master.mergeoptions "" && test_tick && git merge --no-ff c1 && verify_merge file result.1 && @@ -427,6 +428,11 @@ test_expect_success 'merge c0 with c1 (no-ff)' ' test_debug 'gitk --all' +test_expect_success 'combining --squash and --no-ff is refused' ' + test_must_fail git merge --squash --no-ff c1 && + test_must_fail git merge --no-ff --squash c1 +' + test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && git config branch.master.mergeoptions "--no-ff" && diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh new file mode 100644 index 0000000000..6b0483f3e9 --- /dev/null +++ b/t/t7610-mergetool.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright (c) 2008 Charles Bailey +# + +test_description='git-mergetool + +Testing basic merge tool invocation' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo master >file1 && + git add file1 && + git commit -m "added file1" && + git checkout -b branch1 master && + echo branch1 change >file1 && + echo branch1 newfile >file2 && + git add file1 file2 && + git commit -m "branch1 changes" && + git checkout -b branch2 master && + echo branch2 change >file1 && + echo branch2 newfile >file2 && + git add file1 file2 && + git commit -m "branch2 changes" && + git checkout master && + echo master updated >file1 && + echo master new >file2 && + git add file1 file2 && + git commit -m "master updates" +' + +test_expect_success 'custom mergetool' ' + git config merge.tool mytool && + git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && + git config mergetool.mytool.trustExitCode true && + git checkout branch1 && + ! git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file1>/dev/null 2>&1 ) && + ( yes "" | git mergetool file2>/dev/null 2>&1 ) && + test "$(cat file1)" = "master updated" && + test "$(cat file2)" = "master new" && + git commit -m "branch1 resolved with mergetool" +' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index cceedbb2b7..c4f4465dc6 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -869,6 +869,8 @@ zcommits COMMIT reset refs/tags/O3-2nd from :5 +reset refs/tags/O3-3rd +from :5 INPUT_END cat >expect <<INPUT_END diff --git a/t/test-lib.sh b/t/test-lib.sh index 87a5ea4a6a..6aea0ea0a5 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -3,12 +3,16 @@ # Copyright (c) 2005 Junio C Hamano # +# Keep the original TERM for say_color +ORIGINAL_TERM=$TERM + # For repeatability, reset the environment to known value. LANG=C LC_ALL=C PAGER=cat TZ=UTC -export LANG LC_ALL PAGER TZ +TERM=dumb +export LANG LC_ALL PAGER TERM TZ EDITOR=: VISUAL=: unset GIT_EDITOR @@ -58,12 +62,14 @@ esac # This test checks if command xyzzy does the right thing... # ' # . ./test-lib.sh - -[ "x$TERM" != "xdumb" ] && - [ -t 1 ] && - tput bold >/dev/null 2>&1 && - tput setaf 1 >/dev/null 2>&1 && - tput sgr0 >/dev/null 2>&1 && +[ "x$ORIGINAL_TERM" != "xdumb" ] && ( + TERM=$ORIGINAL_TERM && + export TERM && + [ -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 @@ -91,6 +97,9 @@ done if test -n "$color"; then say_color () { + ( + TERM=$ORIGINAL_TERM + export TERM case "$1" in error) tput bold; tput setaf 1;; # bold red skip) tput bold; tput setaf 2;; # bold green @@ -101,6 +110,7 @@ if test -n "$color"; then shift echo "* $*" tput sgr0 + ) } else say_color() { diff --git a/test-parse-options.c b/test-parse-options.c index eed8a02c65..73360d7512 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -20,6 +20,8 @@ int main(int argc, const char **argv) OPT_STRING(0, "string2", &string, "str", "get another string"), OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), OPT_STRING('o', NULL, &string, "str", "get another string"), + OPT_GROUP("magic arguments"), + OPT_ARGUMENT("quux", "means --quux"), OPT_END(), }; int i; diff --git a/transport.c b/transport.c index 166c1d1d46..393e0e8fe2 100644 --- a/transport.c +++ b/transport.c @@ -560,6 +560,7 @@ static int close_bundle(struct transport *transport) struct git_transport_data { unsigned thin : 1; unsigned keep : 1; + unsigned followtags : 1; int depth; struct child_process *conn; int fd[2]; @@ -580,6 +581,9 @@ static int set_git_option(struct transport *connection, } else if (!strcmp(name, TRANS_OPT_THIN)) { data->thin = !!value; return 0; + } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) { + data->followtags = !!value; + return 0; } else if (!strcmp(name, TRANS_OPT_KEEP)) { data->keep = !!value; return 0; @@ -628,6 +632,7 @@ static int fetch_refs_via_pack(struct transport *transport, args.keep_pack = data->keep; args.lock_pack = 1; args.use_thin_pack = data->thin; + args.include_tag = data->followtags; args.verbose = transport->verbose > 0; args.depth = data->depth; diff --git a/transport.h b/transport.h index 6fb4526cda..8abfc0ae60 100644 --- a/transport.h +++ b/transport.h @@ -53,6 +53,9 @@ struct transport *transport_get(struct remote *, const char *); /* Limit the depth of the fetch if not null */ #define TRANS_OPT_DEPTH "depth" +/* Aggressively fetch annotated tags if possible */ +#define TRANS_OPT_FOLLOWTAGS "followtags" + /** * Returns 0 if the option was used, non-zero otherwise. Prints a * message to stderr if the option is not used. diff --git a/tree-walk.c b/tree-walk.c index 142205ddc3..02e2aed773 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -62,7 +62,7 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1) static int entry_compare(struct name_entry *a, struct name_entry *b) { - return base_name_compare( + return df_name_compare( a->path, tree_entry_len(a->path, a->sha1), a->mode, b->path, tree_entry_len(b->path, b->sha1), b->mode); } @@ -104,12 +104,48 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry) return 1; } -void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback) +void setup_traverse_info(struct traverse_info *info, const char *base) { + int pathlen = strlen(base); + static struct traverse_info dummy; + + memset(info, 0, sizeof(*info)); + if (pathlen && base[pathlen-1] == '/') + pathlen--; + info->pathlen = pathlen ? pathlen + 1 : 0; + info->name.path = base; + info->name.sha1 = (void *)(base + pathlen + 1); + if (pathlen) + info->prev = &dummy; +} + +char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n) +{ + int len = tree_entry_len(n->path, n->sha1); + int pathlen = info->pathlen; + + path[pathlen + len] = 0; + for (;;) { + memcpy(path + pathlen, n->path, len); + if (!pathlen) + break; + path[--pathlen] = '/'; + n = &info->name; + len = tree_entry_len(n->path, n->sha1); + info = info->prev; + pathlen -= len; + } + return path; +} + +int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) +{ + int ret = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); for (;;) { unsigned long mask = 0; + unsigned long dirmask = 0; int i, last; last = -1; @@ -134,25 +170,35 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb mask = 0; } mask |= 1ul << i; + if (S_ISDIR(entry[i].mode)) + dirmask |= 1ul << i; last = i; } if (!mask) break; + dirmask &= mask; /* - * Update the tree entries we've walked, and clear - * all the unused name-entries. + * Clear all the unused name-entries. */ for (i = 0; i < n; i++) { - if (mask & (1ul << i)) { - update_tree_entry(t+i); + if (mask & (1ul << i)) continue; - } entry_clear(entry + i); } - callback(n, mask, entry, base); + ret = info->fn(n, mask, dirmask, entry, info); + if (ret < 0) + break; + if (ret) + mask &= ret; + ret = 0; + for (i = 0; i < n; i++) { + if (mask & (1ul << i)) + update_tree_entry(t + i); + } } free(entry); + return ret; } static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) diff --git a/tree-walk.h b/tree-walk.h index db0fbdc701..42110a465f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -33,10 +33,27 @@ int tree_entry(struct tree_desc *, struct name_entry *); void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); -typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base); - -void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback); +struct traverse_info; +typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); +int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); + +struct traverse_info { + struct traverse_info *prev; + struct name_entry name; + int pathlen; + + unsigned long conflicts; + traverse_callback_t fn; + void *data; +}; int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); +extern char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n); +extern void setup_traverse_info(struct traverse_info *info, const char *base); + +static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n) +{ + return info->pathlen + tree_entry_len(n->path, n->sha1); +} #endif diff --git a/unpack-trees.c b/unpack-trees.c index 3e448d8974..93019abdc1 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,3 +1,4 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "dir.h" #include "tree.h" @@ -7,268 +8,18 @@ #include "progress.h" #include "refs.h" -#define DBRT_DEBUG 1 - -struct tree_entry_list { - struct tree_entry_list *next; - unsigned int mode; - const char *name; - const unsigned char *sha1; -}; - -static struct tree_entry_list *create_tree_entry_list(struct tree_desc *desc) -{ - struct name_entry one; - struct tree_entry_list *ret = NULL; - struct tree_entry_list **list_p = &ret; - - while (tree_entry(desc, &one)) { - struct tree_entry_list *entry; - - entry = xmalloc(sizeof(struct tree_entry_list)); - entry->name = one.path; - entry->sha1 = one.sha1; - entry->mode = one.mode; - entry->next = NULL; - - *list_p = entry; - list_p = &entry->next; - } - return ret; -} - -static int entcmp(const char *name1, int dir1, const char *name2, int dir2) +static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, + unsigned int set, unsigned int clear) { - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - -static inline void remove_entry(int remove) -{ - if (remove >= 0) - remove_cache_entry_at(remove); -} - -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, struct unpack_trees_options *o, - struct tree_entry_list *df_conflict_list) -{ - int remove; - int baselen = strlen(base); - int src_size = len + 1; - int retval = 0; - - do { - int i; - const char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - int skip_entry = 0; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (o->merge && o->pos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[o->pos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - fprintf(stderr, "index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - fprintf(stderr, "%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - S_ISDIR(posns[i]->mode)) > 0) { - first = posns[i]->name; - firstdir = S_ISDIR(posns[i]->mode); - } - } - /* No name means we're done */ - if (!first) - goto leave_directory; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - remove = -1; - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[o->pos]; - remove = o->pos; - if (o->skip_unmerged && ce_stage(src[0])) - skip_entry = 1; - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == df_conflict_list) { - src[i + o->merge] = o->df_conflict_entry; - continue; - } + unsigned int size = ce_size(ce); + struct cache_entry *new = xmalloc(size); - if (S_ISDIR(posns[i]->mode)) { - struct tree *tree = lookup_tree(posns[i]->sha1); - struct tree_desc t; - any_dirs = 1; - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - subposns[i] = create_tree_entry_list(&t); - posns[i] = posns[i]->next; - src[i + o->merge] = o->df_conflict_entry; - continue; - } - - if (skip_entry) { - subposns[i] = df_conflict_list; - posns[i] = posns[i]->next; - continue; - } - - if (!o->merge) - ce_stage = 0; - else if (i + 1 < o->head_idx) - ce_stage = 1; - else if (i + 1 > o->head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - hashcpy(ce->sha1, posns[i]->sha1); - src[i + o->merge] = ce; - subposns[i] = df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (skip_entry) { - o->pos++; - while (o->pos < active_nr && - !strcmp(active_cache[o->pos]->name, - src[0]->name)) - o->pos++; - } else if (o->merge) { - int ret; - -#if DBRT_DEBUG > 1 - fprintf(stderr, "%s:\n", first); - for (i = 0; i < src_size; i++) { - fprintf(stderr, " %d ", i); - if (src[i]) - fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1)); - else - fprintf(stderr, "\n"); - } -#endif - ret = o->fn(src, o, remove); - if (ret < 0) - return ret; + clear |= CE_HASHED | CE_UNHASHED; -#if DBRT_DEBUG > 1 - fprintf(stderr, "Added %d entries\n", ret); -#endif - o->pos += ret; - } else { - remove_entry(remove); - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, o, - df_conflict_list)) { - retval = -1; - goto leave_directory; - } - free(newbase); - } - free(subposns); - free(src); - } while (1); - - leave_directory: - return retval; + memcpy(new, ce, size); + new->next = NULL; + new->ce_flags = (new->ce_flags & ~clear) | set; + add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK); } /* Unlink the last component and attempt to remove leading @@ -308,11 +59,12 @@ static void check_updates(struct unpack_trees_options *o) unsigned cnt = 0, total = 0; struct progress *progress = NULL; char last_symlink[PATH_MAX]; + struct index_state *index = &o->result; int i; if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < active_nr; cnt++) { - struct cache_entry *ce = active_cache[cnt]; + for (total = cnt = 0; cnt < index->cache_nr; cnt++) { + struct cache_entry *ce = index->cache[cnt]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) total++; } @@ -323,15 +75,15 @@ static void check_updates(struct unpack_trees_options *o) } *last_symlink = '\0'; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < index->cache_nr; i++) { + struct cache_entry *ce = index->cache[i]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) display_progress(progress, ++cnt); if (ce->ce_flags & CE_REMOVE) { if (o->update) unlink_entry(ce->name, last_symlink); - remove_cache_entry_at(i); + remove_index_entry_at(&o->result, i); i--; continue; } @@ -346,21 +98,246 @@ static void check_updates(struct unpack_trees_options *o) stop_progress(&progress); } -int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) +static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) +{ + int ret = o->fn(src, o); + if (ret > 0) + ret = 0; + return ret; +} + +static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) +{ + struct cache_entry *src[5] = { ce, }; + + o->pos++; + if (ce_stage(ce)) { + if (o->skip_unmerged) { + add_entry(o, ce, 0, 0); + return 0; + } + } + return call_unpack_fn(src, o); +} + +int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) { - struct tree_entry_list **posns; int i; - struct tree_entry_list df_conflict_list; + struct tree_desc t[MAX_UNPACK_TREES]; + struct traverse_info newinfo; + struct name_entry *p; + + p = names; + while (!p->mode) + p++; + + newinfo = *info; + newinfo.prev = info; + newinfo.name = *p; + newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1; + newinfo.conflicts |= df_conflicts; + + for (i = 0; i < n; i++, dirmask >>= 1) { + const unsigned char *sha1 = NULL; + if (dirmask & 1) + sha1 = names[i].sha1; + fill_tree_descriptor(t+i, sha1); + } + return traverse_trees(n, t, &newinfo); +} + +/* + * Compare the traverse-path to the cache entry without actually + * having to generate the textual representation of the traverse + * path. + * + * NOTE! This *only* compares up to the size of the traverse path + * itself - the caller needs to do the final check for the cache + * entry having more data at the end! + */ +static int do_compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) +{ + int len, pathlen, ce_len; + const char *ce_name; + + if (info->prev) { + int cmp = do_compare_entry(ce, info->prev, &info->name); + if (cmp) + return cmp; + } + pathlen = info->pathlen; + ce_len = ce_namelen(ce); + + /* If ce_len < pathlen then we must have previously hit "name == directory" entry */ + if (ce_len < pathlen) + return -1; + + ce_len -= pathlen; + ce_name = ce->name + pathlen; + + len = tree_entry_len(n->path, n->sha1); + return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode); +} + +static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) +{ + int cmp = do_compare_entry(ce, info, n); + if (cmp) + return cmp; + + /* + * Even if the beginning compared identically, the ce should + * compare as bigger than a directory leading up to it! + */ + return ce_namelen(ce) > traverse_path_len(info, n); +} + +static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage) +{ + int len = traverse_path_len(info, n); + struct cache_entry *ce = xcalloc(1, cache_entry_size(len)); + + ce->ce_mode = create_ce_mode(n->mode); + ce->ce_flags = create_ce_flags(len, stage); + hashcpy(ce->sha1, n->sha1); + make_traverse_path(ce->name, info, n); + + return ce; +} + +static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5], + const struct name_entry *names, const struct traverse_info *info) +{ + int i; + struct unpack_trees_options *o = info->data; + unsigned long conflicts; + + /* Do we have *only* directories? Nothing to do */ + if (mask == dirmask && !src[0]) + return 0; + + conflicts = info->conflicts; + if (o->merge) + conflicts >>= 1; + conflicts |= dirmask; + + /* + * Ok, we've filled in up to any potential index entry in src[0], + * now do the rest. + */ + for (i = 0; i < n; i++) { + int stage; + unsigned int bit = 1ul << i; + if (conflicts & bit) { + src[i + o->merge] = o->df_conflict_entry; + continue; + } + if (!(mask & bit)) + continue; + if (!o->merge) + stage = 0; + else if (i + 1 < o->head_idx) + stage = 1; + else if (i + 1 > o->head_idx) + stage = 3; + else + stage = 2; + src[i + o->merge] = create_ce_entry(info, names + i, stage); + } + + if (o->merge) + return call_unpack_fn(src, o); + + n += o->merge; + for (i = 0; i < n; i++) + add_entry(o, src[i], 0, 0); + return 0; +} + +static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) +{ + struct cache_entry *src[5] = { NULL, }; + struct unpack_trees_options *o = info->data; + const struct name_entry *p = names; + + /* Find first entry with a real name (we could use "mask" too) */ + while (!p->mode) + p++; + + /* Are we supposed to look at the index too? */ + if (o->merge) { + while (o->pos < o->src_index->cache_nr) { + struct cache_entry *ce = o->src_index->cache[o->pos]; + int cmp = compare_entry(ce, info, p); + if (cmp < 0) { + if (unpack_index_entry(ce, o) < 0) + return -1; + continue; + } + if (!cmp) { + o->pos++; + if (ce_stage(ce)) { + /* + * If we skip unmerged index entries, we'll skip this + * entry *and* the tree entries associated with it! + */ + if (o->skip_unmerged) { + add_entry(o, ce, 0, 0); + return mask; + } + } + src[0] = ce; + } + break; + } + } + + if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) + return -1; + + /* Now handle any directories.. */ + if (dirmask) { + unsigned long conflicts = mask & ~dirmask; + if (o->merge) { + conflicts <<= 1; + if (src[0]) + conflicts |= 1; + } + if (traverse_trees_recursive(n, dirmask, conflicts, + names, info) < 0) + return -1; + return mask; + } + + return mask; +} + +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + discard_index(&o->result); + if (!o->gently) { + if (message) + return error(message); + return -1; + } + return -1; +} + +int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) +{ static struct cache_entry *dfc; - memset(&df_conflict_list, 0, sizeof(df_conflict_list)); - df_conflict_list.next = &df_conflict_list; + if (len > MAX_UNPACK_TREES) + die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); memset(&state, 0, sizeof(state)); state.base_dir = ""; state.force = 1; state.quiet = 1; state.refresh_cache = 1; + memset(&o->result, 0, sizeof(o->result)); + if (o->src_index) + o->result.timestamp = o->src_index->timestamp; o->merge_size = len; if (!dfc) @@ -368,30 +345,33 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->df_conflict_entry = dfc; if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) - posns[i] = create_tree_entry_list(t+i); - - if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", - o, &df_conflict_list)) { - if (o->gently) { - discard_cache(); - read_cache(); - } - return -1; - } + const char *prefix = o->prefix ? o->prefix : ""; + struct traverse_info info; + + setup_traverse_info(&info, prefix); + info.fn = unpack_callback; + info.data = o; + + if (traverse_trees(len, t, &info) < 0) + return unpack_failed(o, NULL); } - if (o->trivial_merges_only && o->nontrivial_merge) { - if (o->gently) { - discard_cache(); - read_cache(); + /* Any left-over entries in the index? */ + if (o->merge) { + while (o->pos < o->src_index->cache_nr) { + struct cache_entry *ce = o->src_index->cache[o->pos]; + if (unpack_index_entry(ce, o) < 0) + return unpack_failed(o, NULL); } - return o->gently ? -1 : - error("Merge requires file-level merging"); } + if (o->trivial_merges_only && o->nontrivial_merge) + return unpack_failed(o, "Merge requires file-level merging"); + + o->src_index = NULL; check_updates(o); + if (o->dst_index) + *o->dst_index = o->result; return 0; } @@ -427,7 +407,7 @@ static int verify_uptodate(struct cache_entry *ce, return 0; if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) return 0; /* @@ -447,10 +427,10 @@ static int verify_uptodate(struct cache_entry *ce, error("Entry '%s' not uptodate. Cannot merge.", ce->name); } -static void invalidate_ce_path(struct cache_entry *ce) +static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) - cache_tree_invalidate_path(active_cache_tree, ce->name); + cache_tree_invalidate_path(o->src_index->cache_tree, ce->name); } /* @@ -495,12 +475,12 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - pos = cache_name_pos(ce->name, namelen); + pos = index_name_pos(o->src_index, ce->name, namelen); if (0 <= pos) return cnt; /* we have it as nondirectory */ pos = -pos - 1; - for (i = pos; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = pos; i < o->src_index->cache_nr; i++) { + struct cache_entry *ce = o->src_index->cache[i]; int len = ce_namelen(ce); if (len < namelen || strncmp(ce->name, ce->name, namelen) || @@ -512,7 +492,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, if (!ce_stage(ce)) { if (verify_uptodate(ce, o)) return -1; - ce->ce_flags |= CE_REMOVE; + add_entry(o, ce, CE_REMOVE, 0); } cnt++; } @@ -598,9 +578,9 @@ static int verify_absent(struct cache_entry *ce, const char *action, * delete this path, which is in a subdirectory that * is being replaced with a blob. */ - cnt = cache_name_pos(ce->name, strlen(ce->name)); + cnt = index_name_pos(&o->result, ce->name, strlen(ce->name)); if (0 <= cnt) { - struct cache_entry *ce = active_cache[cnt]; + struct cache_entry *ce = o->result.cache[cnt]; if (ce->ce_flags & CE_REMOVE) return 0; } @@ -615,7 +595,6 @@ static int verify_absent(struct cache_entry *ce, const char *action, static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { - merge->ce_flags |= CE_UPDATE; if (old) { /* * See if we can re-use the old CE directly? @@ -629,38 +608,38 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; - invalidate_ce_path(old); + invalidate_ce_path(old, o); } } else { if (verify_absent(merge, "overwritten", o)) return -1; - invalidate_ce_path(merge); + invalidate_ce_path(merge, o); } - merge->ce_flags &= ~CE_STAGEMASK; - add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + add_entry(o, merge, CE_UPDATE, CE_STAGEMASK); return 1; } static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, struct unpack_trees_options *o) { - if (old) { - if (verify_uptodate(old, o)) - return -1; - } else + /* Did it exist in the index? */ + if (!old) { if (verify_absent(ce, "removed", o)) return -1; - ce->ce_flags |= CE_REMOVE; - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); + return 0; + } + if (verify_uptodate(old, o)) + return -1; + add_entry(o, ce, CE_REMOVE, 0); + invalidate_ce_path(ce, o); return 1; } static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o) { - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + add_entry(o, ce, 0, 0); return 1; } @@ -680,9 +659,7 @@ static void show_stage_entry(FILE *o, } #endif -int threeway_merge(struct cache_entry **stages, - struct unpack_trees_options *o, - int remove) +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) { struct cache_entry *index; struct cache_entry *head; @@ -759,10 +736,8 @@ int threeway_merge(struct cache_entry **stages, } /* #1 */ - if (!head && !remote && any_anc_missing) { - remove_entry(remove); + if (!head && !remote && any_anc_missing) return 0; - } /* Under the new "aggressive" rule, we resolve mostly trivial * cases that we historically had git-merge-one-file resolve. @@ -794,10 +769,9 @@ int threeway_merge(struct cache_entry **stages, if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || (remote_deleted && head && head_match)) { - remove_entry(remove); if (index) return deleted_entry(index, index, o); - else if (ce && !head_deleted) { + if (ce && !head_deleted) { if (verify_absent(ce, "removed", o)) return -1; } @@ -820,7 +794,6 @@ int threeway_merge(struct cache_entry **stages, return -1; } - remove_entry(remove); o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #10, #11. */ @@ -855,9 +828,7 @@ int threeway_merge(struct cache_entry **stages, * "carry forward" rule, please see <Documentation/git-read-tree.txt>. * */ -int twoway_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *current = src[0]; struct cache_entry *oldtree = src[1]; @@ -885,7 +856,6 @@ int twoway_merge(struct cache_entry **src, } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ - remove_entry(remove); return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && @@ -895,7 +865,6 @@ int twoway_merge(struct cache_entry **src, } else { /* all other failures */ - remove_entry(remove); if (oldtree) return o->gently ? -1 : reject_merge(oldtree); if (current) @@ -907,7 +876,6 @@ int twoway_merge(struct cache_entry **src, } else if (newtree) return merged_entry(newtree, current, o); - remove_entry(remove); return deleted_entry(oldtree, current, o); } @@ -918,8 +886,7 @@ int twoway_merge(struct cache_entry **src, * stage0 does not have anything there. */ int bind_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) + struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -929,7 +896,7 @@ int bind_merge(struct cache_entry **src, o->merge_size); if (a && old) return o->gently ? -1 : - error("Entry '%s' overlaps. Cannot bind.", a->name); + error("Entry '%s' overlaps with '%s'. Cannot bind.", a->name, old->name); if (!a) return keep_entry(old, o); else @@ -942,9 +909,7 @@ int bind_merge(struct cache_entry **src, * The rule is: * - take the stat information from stage0, take the data from stage1 */ -int oneway_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -953,18 +918,19 @@ int oneway_merge(struct cache_entry **src, return error("Cannot do a oneway merge of %d trees", o->merge_size); - if (!a) { - remove_entry(remove); + if (!a) return deleted_entry(old, old, o); - } + if (old && same(old, a)) { + int update = 0; if (o->reset) { struct stat st; if (lstat(old->name, &st) || - ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID)) - old->ce_flags |= CE_UPDATE; + ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) + update |= CE_UPDATE; } - return keep_entry(old, o); + add_entry(o, old, update, 0); + return 0; } return merged_entry(a, old, o); } diff --git a/unpack-trees.h b/unpack-trees.h index a2df544d04..50453ed20f 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -1,11 +1,12 @@ #ifndef UNPACK_TREES_H #define UNPACK_TREES_H +#define MAX_UNPACK_TREES 8 + struct unpack_trees_options; typedef int (*merge_fn_t)(struct cache_entry **src, - struct unpack_trees_options *options, - int remove); + struct unpack_trees_options *options); struct unpack_trees_options { int reset; @@ -28,14 +29,18 @@ struct unpack_trees_options { struct cache_entry *df_conflict_entry; void *unpack_data; + + struct index_state *dst_index; + const struct index_state *src_index; + struct index_state result; }; extern int unpack_trees(unsigned n, struct tree_desc *t, struct unpack_trees_options *options); -int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o, int); -int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); -int bind_merge(struct cache_entry **src, struct unpack_trees_options *o, int); -int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); #endif diff --git a/upload-pack.c b/upload-pack.c index e5421db9c5..b46dd365ea 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -27,7 +27,8 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n static unsigned long oldest_have; static int multi_ack, nr_our_refs; -static int use_thin_pack, use_ofs_delta, no_progress; +static int use_thin_pack, use_ofs_delta, use_include_tag; +static int no_progress; static struct object_array have_obj; static struct object_array want_obj; static unsigned int timeout; @@ -35,6 +36,7 @@ static unsigned int timeout; * otherwise maximum packet size (up to 65520 bytes). */ static int use_sideband; +static int debug_fd; static void reset_timeout(void) { @@ -161,6 +163,8 @@ static void create_pack_file(void) argv[arg++] = "--progress"; if (use_ofs_delta) argv[arg++] = "--delta-base-offset"; + if (use_include_tag) + argv[arg++] = "--include-tag"; argv[arg++] = NULL; memset(&pack_objects, 0, sizeof(pack_objects)); @@ -444,6 +448,8 @@ static void receive_needs(void) static char line[1000]; int len, depth = 0; + if (debug_fd) + write_in_full(debug_fd, "#S\n", 3); for (;;) { struct object *o; unsigned char sha1_buf[20]; @@ -451,6 +457,8 @@ static void receive_needs(void) reset_timeout(); if (!len) break; + if (debug_fd) + write_in_full(debug_fd, line, len); if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; @@ -489,6 +497,8 @@ static void receive_needs(void) use_sideband = DEFAULT_PACKET_MAX; if (strstr(line+45, "no-progress")) no_progress = 1; + if (strstr(line+45, "include-tag")) + use_include_tag = 1; /* We have sent all our refs already, and the other end * should have chosen out of them; otherwise they are @@ -506,6 +516,8 @@ static void receive_needs(void) add_object_array(o, NULL, &want_obj); } } + if (debug_fd) + write_in_full(debug_fd, "#E\n", 3); if (depth == 0 && shallows.nr == 0) return; if (depth > 0) { @@ -558,7 +570,8 @@ static void receive_needs(void) static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { static const char *capabilities = "multi_ack thin-pack side-band" - " side-band-64k ofs-delta shallow no-progress"; + " side-band-64k ofs-delta shallow no-progress" + " include-tag"; struct object *o = parse_object(sha1); if (!o) @@ -631,6 +644,8 @@ int main(int argc, char **argv) die("'%s': unable to chdir or not a git archive", dir); if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); + if (getenv("GIT_DEBUG_SEND_PACK")) + debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); upload_pack(); return 0; } diff --git a/wt-status.c b/wt-status.c index 32d780af1e..b3fd57b79d 100644 --- a/wt-status.c +++ b/wt-status.c @@ -7,6 +7,7 @@ #include "diff.h" #include "revision.h" #include "diffcore.h" +#include "quote.h" int wt_status_relative_paths = 1; int wt_status_use_color = -1; @@ -82,51 +83,7 @@ static void wt_status_print_trailer(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } -static char *quote_path(const char *in, int len, - struct strbuf *out, const char *prefix) -{ - if (len < 0) - len = strlen(in); - - strbuf_grow(out, len); - strbuf_setlen(out, 0); - if (prefix) { - int off = 0; - while (prefix[off] && off < len && prefix[off] == in[off]) - if (prefix[off] == '/') { - prefix += off + 1; - in += off + 1; - len -= off + 1; - off = 0; - } else - off++; - - for (; *prefix; prefix++) - if (*prefix == '/') - strbuf_addstr(out, "../"); - } - - for ( ; len > 0; in++, len--) { - int ch = *in; - - switch (ch) { - case '\n': - strbuf_addstr(out, "\\n"); - break; - case '\r': - strbuf_addstr(out, "\\r"); - break; - default: - strbuf_addch(out, ch); - continue; - } - } - - if (!out->len) - strbuf_addstr(out, "./"); - - return out->buf; -} +#define quote_path quote_path_relative static void wt_status_print_filepair(struct wt_status *s, int t, struct diff_filepair *p) @@ -312,27 +269,14 @@ static void wt_status_print_untracked(struct wt_status *s) static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; - int saved_stdout; - - fflush(s->fp); - - /* Sigh, the entire diff machinery is hardcoded to output to - * stdout. Do the dup-dance...*/ - saved_stdout = dup(STDOUT_FILENO); - if (saved_stdout < 0 ||dup2(fileno(s->fp), STDOUT_FILENO) < 0) - die("couldn't redirect stdout\n"); init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, s->reference); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; + rev.diffopt.file = s->fp; + rev.diffopt.close_file = 0; run_diff_index(&rev, 1); - - fflush(stdout); - - if (dup2(saved_stdout, STDOUT_FILENO) < 0) - die("couldn't restore stdout\n"); - close(saved_stdout); } void wt_status_print(struct wt_status *s) diff --git a/xdiff-interface.c b/xdiff-interface.c index bba236428a..61dc5c5470 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -152,8 +152,8 @@ int read_mmfile(mmfile_t *ptr, const char *filename) if ((f = fopen(filename, "rb")) == NULL) return error("Could not open %s", filename); sz = xsize_t(st.st_size); - ptr->ptr = xmalloc(sz); - if (fread(ptr->ptr, sz, 1, f) != 1) + ptr->ptr = xmalloc(sz ? sz : 1); + if (sz && fread(ptr->ptr, sz, 1, f) != 1) return error("Could not read %s", filename); fclose(f); ptr->size = sz; |