/git-cherry-pick
/git-clean
/git-clone
+/git-column
/git-commit
/git-commit-tree
/git-config
/git-name-rev
/git-mv
/git-notes
+/git-p4
/git-pack-redundant
/git-pack-objects
/git-pack-refs
/test-index-version
/test-line-buffer
/test-match-trees
+/test-mergesort
/test-mktemp
/test-parse-options
/test-path-utils
+/test-revision-walking
/test-run-command
/test-sha1
/test-sigchain
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+ifdef DEFAULT_PAGER
+DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER))
+ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)'
+endif
+
+ifdef DEFAULT_EDITOR
+DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR))
+ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)'
+endif
+
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
--- /dev/null
+Git v1.7.11 Release Notes
+=========================
+
+Updates since v1.7.10
+---------------------
+
+UI, Workflows & Features
+
+ * A new mode for push, "simple", which is a cross between "current"
+ and "upstream", has been introduced. "git push" without any refspec
+ will push the current branch out to the same name at the remote
+ repository only when it is set to track the branch with the same
+ name over there. The plan is to make this mode the new default
+ value when push.default is not configured.
+
+ * A couple of commands learned the "--column" option to produce
+ columnar output.
+
+ * A third-party tool "git subtree" is distributed in contrib/
+
+ * Error messages given when @{u} is used for a branch without its
+ upstream configured have been clatified.
+
+ * Even with "-q"uiet option, "checkout" used to report setting up
+ tracking. Also "branch" learned the "-q"uiet option to squelch
+ informational message.
+
+ * Your build platform may support hardlinks but you may prefer not to
+ use them, e.g. when installing to DESTDIR to make a tarball and
+ untarring on a filesystem that has poor support for hardlinks.
+ There is a Makefile option NO_INSTALL_HARDLINKS for you.
+
+ * The smart-http backend used to always override GIT_COMMITTER_*
+ variables with REMOTE_USER and REMOTE_ADDR, but these variables are
+ now preserved when set.
+
+ * "git am" learned the "--include" option, which is an opposite of
+ existing the "--exclude" option.
+
+ * When "git am -3" needs to fall back to an application to a
+ synthesized preimage followed by a 3-way merge, the paths that
+ needed such treatment are now reported to the end user, so that the
+ result in them can be eyeballed with extra care.
+
+ * The output from "diff/log --stat" used to always allocate 4 columns
+ to show the number of modified lines, but not anymore.
+
+ * "git difftool" learned the "--dir-diff" option to spawn external
+ diff tools that can compare two directory hierarchies at a time
+ after populating two temporary directories, instead of running an
+ instance of the external tool once per a file pair.
+
+ * The "fmt-merge-msg" command learns to list the primary contributors
+ involved in the side topic you are merging.
+
+ * "git rebase" learned to optionally keep commits that do not
+ introduce any change in the original history.
+
+ * "git push --recurse-submodules" learned to optionally look into the
+ histories of submodules bound to the superproject and push them
+ out.
+
+ * A 'snapshot' request to "gitweb" honors If-Modified-Since: header,
+ based on the commit date.
+
+ * "gitweb" learned to highlight the patch it outputs even more.
+
+Foreign Interface
+
+ * "git svn" used to die with unwanted SIGPIPE when talking with HTTP
+ server that uses keep-alive.
+
+ * "git svn" learned to use platform specific authentication
+ providers, e.g. gnome-keyring, kwallet, etc.
+
+ * "git p4" has been moved out of contrib/ area and has seen more work
+ on importing labels as tags from (and exporting tags as labels to)
+ p4.
+
+Performance and Internal Implementation (please report possible regressions)
+
+ * An experimental "version 4" format of the index file has been
+ introduced to reduce on-disk footprint and I/O overhead.
+
+ * "git archive" learned to produce its output without reading the
+ blob object it writes out in memory in its entirety.
+
+ * "git index-pack" that runs when fetching or pushing objects to
+ complete the packfile on the receiving end learned to use multiple
+ threads to do its job when available.
+
+ * The code to compute hash values for lines used by the internal diff
+ engine was optimized on little-endian machines, using the same
+ trick the kernel folks came up with.
+
+ * "git apply" had some memory leaks plugged.
+
+ * Setting up a revision traversal with many starting points was
+ inefficient as these were placed in a date-order priority queue
+ one-by-one. Now they are collected in the queue unordered first,
+ and sorted immediately before getting used.
+
+ * More lower-level commands learned to use the streaming API to read
+ from the object store without keeping everything in core.
+
+ * Because "sh" on the user's PATH may be utterly broken on some
+ systems, run-command API now uses SHELL_PATH, not /bin/sh, when
+ spawning an external command (not applicable to Windows port).
+
+ * The API to iterate over refs/ hierarchy has been tweaked to allow
+ walking only a subset of it more efficiently.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.10
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.10 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * When a submodule repository uses alternate object store mechanism,
+ some commands that were started from the superproject did not
+ notice it and failed with "No such object" errors. The subcommands
+ of "git submodule" command that properly recursed into the
+ submodule in a separate process were OK; only the ones that cheated
+ and peeked directly into the submodule's repository from the
+ primary process were affected.
+ (merge 5e73633 hv/submodule-alt-odb later to maint).
+
+ * The directory path used in "git diff --no-index", when it recurses
+ down, was broken with a recent update after v1.7.10.1 release.
+ (merge 176a335 bp/diff-no-index-strbuf-fix later to maint).
+
+ * "git status --porcelain" ignored "--branch" option by mistake. The
+ output for "git status --branch -z" was also incorrect and did not
+ terminate the record for the current branch name with NUL as asked.
+ (merge d4a6bf1 jk/maint-status-porcelain-z-b later to maint).
+
+ * "git diff --stat" used to fully count a binary file with modified
+ execution bits whose contents is unmodified, which was not quite
+ right.
found at the location of the include directive. If the value of the
`include.path` variable is a relative path, the path is considered to be
relative to the configuration file in which the include directive was
-found. The value of `include.path` is subject to tilde expansion: `{tilde}/`
-is expanded to the value of `$HOME`, and `{tilde}user/` to the specified
+found. The value of `include.path` is subject to tilde expansion: `~/`
+is expanded to the value of `$HOME`, and `~user/` to the specified
user's home directory. See below for examples.
Example
`never` if you prefer git commands not to use color unless enabled
explicitly with some other configuration or the `--color` option.
+column.ui::
+ Specify whether supported commands should output in columns.
+ This variable consists of a list of tokens separated by spaces
+ or commas:
++
+--
+`always`;;
+ always show in columns
+`never`;;
+ never show in columns
+`auto`;;
+ show in columns if the output is to the terminal
+`column`;;
+ fill columns before rows (default)
+`row`;;
+ fill rows before columns
+`plain`;;
+ show in one column
+`dense`;;
+ make unequal size columns to utilize more space
+`nodense`;;
+ make equal size columns
+--
++
+ This option defaults to 'never'.
+
+column.branch::
+ Specify whether to output branch listing in `git branch` in columns.
+ See `column.ui` for details.
+
+column.status::
+ Specify whether to output untracked files in `git status` in columns.
+ See `column.ui` for details.
+
+column.tag::
+ Specify whether to output tag listing in `git tag` in columns.
+ See `column.ui` for details.
+
commit.status::
A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit
line. Possible values are:
+
* `nothing` - do not push anything.
-* `matching` - push all matching branches.
- All branches having the same name in both ends are considered to be
- matching. This is the default.
+* `matching` - push all branches having the same name in both ends.
+ This is for those who prepare all the branches into a publishable
+ shape and then push them out with a single command. It is not
+ appropriate for pushing into a repository shared by multiple users,
+ since locally stalled branches will attempt a non-fast forward push
+ if other users updated the branch.
+ +
+ This is currently the default, but Git 2.0 will change the default
+ to `simple`.
* `upstream` - push the current branch to its upstream branch.
-* `tracking` - deprecated synonym for `upstream`.
+ With this, `git push` will update the same remote ref as the one which
+ is merged by `git pull`, making `push` and `pull` symmetrical.
+ See "branch.<name>.merge" for how to configure the upstream branch.
+* `simple` - like `upstream`, but refuses to push if the upstream
+ branch's name is different from the local one. This is the safest
+ option and is well-suited for beginners. It will become the default
+ in Git 2.0.
* `current` - push the current branch to a branch of the same name.
+ +
+ The `simple`, `current` and `upstream` modes are for those who want to
+ push out a single branch after finishing work, even when the other
+ branches are not yet ready to be pushed out. If you are working with
+ other people to push into the same shared repository, you would want
+ to use one of these.
rebase.stat::
Whether to show a diffstat of what changed upstream since the last
[--3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
- [--exclude=<path>] [--reject] [-q | --quiet]
+ [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
[--scissors | --no-scissors]
[(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort)
-p<n>::
--directory=<dir>::
--exclude=<path>::
+--include=<path>::
--reject::
These flags are passed to the 'git apply' (see linkgit:git-apply[1])
program that applies
[verse]
'git branch' [--color[=<when>] | --no-color] [-r | -a]
[--list] [-v [--abbrev=<length> | --no-abbrev]]
+ [--column[=<options>] | --no-column]
[(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
default to color output.
Same as `--color=never`.
+--column[=<options>]::
+--no-column::
+ Display branch listing in columns. See configuration variable
+ column.branch for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable in non-verbose mode.
+
-r::
--remotes::
List or delete (if used with -d) the remote-tracking branches.
relationship to upstream branch (if any). If given twice, print
the name of the upstream branch, as well.
+-q::
+--quiet::
+ Be more quiet when creating or deleting a branch, suppressing
+ non-error messages.
+
--abbrev=<length>::
Alter the sha1's minimum display length in the output listing.
The default value is 7 and can be overridden by the `core.abbrev`
cherry-pick'ed commit, then a fast forward to this commit will
be performed.
+--allow-empty::
+ By default, cherry-picking an empty commit will fail,
+ indicating that an explicit invocation of `git commit
+ --allow-empty` is required. This option overrides that
+ behavior, allowing empty commits to be preserved automatically
+ in a cherry-pick. Note that when "--ff" is in effect, empty
+ commits that meet the "fast-forward" requirement will be kept
+ even without this option. Note also, that use of this option only
+ keeps commits that were initially empty (i.e. the commit recorded the
+ same tree as its parent). Commits which are made empty due to a
+ previous commit are dropped. To force the inclusion of those commits
+ use `--keep-redundant-commits`.
+
+--keep-redundant-commits::
+ If a commit being cherry picked duplicates a commit already in the
+ current history, it will become empty. By default these
+ redundant commits are ignored. This option overrides that behavior and
+ creates an empty commit object. Implies `--allow-empty`.
+
--strategy=<strategy>::
Use the given merge strategy. Should only be used once.
See the MERGE STRATEGIES section in linkgit:git-merge[1]
--- /dev/null
+git-column(1)
+=============
+
+NAME
+----
+git-column - Display data in columns
+
+SYNOPSIS
+--------
+[verse]
+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
+ [--indent=<string>] [--nl=<string>] [--pading=<n>]
+
+DESCRIPTION
+-----------
+This command formats its input into multiple columns.
+
+OPTIONS
+-------
+--command=<name>::
+ Look up layout mode using configuration variable column.<name> and
+ column.ui.
+
+--mode=<mode>::
+ Specify layout mode. See configuration variable column.ui for option
+ syntax.
+
+--raw-mode=<n>::
+ Same as --mode but take mode encoded as a number. This is mainly used
+ by other commands that have already parsed layout mode.
+
+--width=<width>::
+ Specify the terminal width. By default 'git column' will detect the
+ terminal width, or fall back to 80 if it is unable to do so.
+
+--indent=<string>::
+ String to be printed at the beginning of each line.
+
+--nl=<N>::
+ String to be printed at the end of each line,
+ including newline character.
+
+--padding=<N>::
+ The number of spaces between columns. One space by default.
+
+
+Author
+------
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
+
+GIT
+---
+Part of the linkgit:git[1] suite
OPTIONS
-------
+-d::
+--dir-diff::
+ Copy the modified files to a temporary location and perform
+ a directory diff on them. This mode never prompts before
+ launching the diff tool.
+
-y::
--no-prompt::
Do not prompt before launching a diff tool.
-t <tool>::
--tool=<tool>::
- Use the diff tool specified by <tool>.
- Valid diff tools are:
- araxis, bc3, deltawalker, diffuse, emerge, ecmerge, gvimdiff,
- kdiff3, kompare, meld, opendiff, p4merge, tkdiff, vimdiff and
- xxdiff.
+ Use the diff tool specified by <tool>. Valid values include
+ emerge, kompare, meld, and vimdiff. Run `git difftool --tool-help`
+ for the list of valid <tool> settings.
+
If a diff tool is not specified, 'git difftool'
will use the configuration variable `diff.tool`. If the
being compared. `$BASE` is provided for compatibility
with custom merge tool commands and has the same value as `$MERGED`.
+--tool-help::
+ Print a list of diff tools that may be used with `--tool`.
+
-x <command>::
--extcmd=<command>::
Specify a custom command for viewing diffs.
options.
--cat-blob-fd=<fd>::
- Specify the file descriptor that will be written to
- when the `cat-blob` command is encountered in the stream.
- The default behaviour is to write to `stdout`.
+ Write responses to `cat-blob` and `ls` queries to the
+ file descriptor <fd> instead of `stdout`. Allows `progress`
+ output intended for the end-user to be separated from other
+ output.
--done::
Require a `done` command at the end of the stream.
accepted. In particular, the `cat-blob` command can be used in the
middle of a commit but not in the middle of a `data` command.
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
`ls`
~~~~
Prints information about the object at a path to a file descriptor
missing SP <path> LF
====
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
`feature`
~~~~~~~~~
Require that fast-import supports the specified feature, or abort if
in use, the `done` command is mandatory and marks the end of the
stream.
+Responses To Commands
+---------------------
+New objects written by fast-import are not available immediately.
+Most fast-import commands have no visible effect until the next
+checkpoint (or completion). The frontend can send commands to
+fill fast-import's input pipe without worrying about how quickly
+they will take effect, which improves performance by simplifying
+scheduling.
+
+For some frontends, though, it is useful to be able to read back
+data from the current repository as it is being updated (for
+example when the source material describes objects in terms of
+patches to be applied to previously imported objects). This can
+be accomplished by connecting the frontend and fast-import via
+bidirectional pipes:
+
+====
+ mkfifo fast-import-output
+ frontend <fast-import-output |
+ git fast-import >fast-import-output
+====
+
+A frontend set up this way can use `progress`, `ls`, and `cat-blob`
+commands to read information from the import in progress.
+
+To avoid deadlock, such frontends must completely consume any
+pending output from `progress`, `ls`, and `cat-blob` before
+performing writes to fast-import that might block.
+
Crash Reports
-------------
If fast-import is supplied invalid input it will terminate with a
--strict::
Die, if the pack contains broken objects or links.
+--threads=<n>::
+ Specifies the number of threads to spawn when resolving
+ deltas. This requires that index-pack be compiled with
+ pthreads otherwise this option is ignored with a warning.
+ This is meant to reduce packing time on multiprocessor
+ machines. The required amount of memory for the delta search
+ window is however multiplied by the number of threads.
+ Specifying 0 will cause git to auto-detect the number of CPU's
+ and use maximum 3 threads.
+
Note
----
EXAMPLE
-------
-* Create an alias for 'git p4', using the full path to the 'git-p4'
- script if needed:
-+
-------------
-$ git config --global alias.p4 '!git-p4'
-------------
-
* Clone a repository:
+
------------
General options
~~~~~~~~~~~~~~~
-All commands except clone accept this option.
+All commands except clone accept these options.
--git-dir <dir>::
Set the 'GIT_DIR' environment variable. See linkgit:git[1].
+--verbose::
+ Provide more progress information.
+
Sync options
~~~~~~~~~~~~
These options can be used in the initial 'clone' as well as in
--silent::
Do not print any progress information.
---verbose::
- Provide more progress information.
-
--detect-labels::
Query p4 for labels associated with the depot paths, and add
- them as tags in git.
+ them as tags in git. Limited usefulness as only imports labels
+ associated with new changelists. Deprecated.
+
+--import-labels::
+ Import labels from p4 into git.
--import-local::
By default, p4 branches are stored in 'refs/remotes/p4/',
~~~~~~~~~~~~~~
These options can be used to modify 'git p4 submit' behavior.
---verbose::
- Provide more progress information.
-
--origin <commit>::
Upstream location from which commits are identified to submit to
p4. By default, this is the most recent p4 commit reachable
Re-author p4 changes before submitting to p4. This option
requires p4 admin privileges.
+--export-labels::
+ Export tags from git as p4 labels. Tags found in git are applied
+ to the perforce working directory.
+
+Rebase options
+~~~~~~~~~~~~~~
+These options can be used to modify 'git p4 rebase' behavior.
+
+--import-labels::
+ Import p4 labels.
DEPOT PATH SYNTAX
-----------------
work properly; the submit command looks only at the variable and does
not have a command-line option.
-The full syntax for a p4 view is documented in 'p4 help views'. Git-p4
+The full syntax for a p4 view is documented in 'p4 help views'. 'Git p4'
knows only a subset of the view syntax. It understands multi-line
mappings, overlays with '+', exclusions with '-' and double-quotes
-around whitespace. Of the possible wildcards, git-p4 only handles
-'...', and only when it is at the end of the path. Git-p4 will complain
+around whitespace. Of the possible wildcards, 'git p4' only handles
+'...', and only when it is at the end of the path. 'Git p4' will complain
if it encounters an unhandled wildcard.
Bugs in the implementation of overlap mappings exist. If multiple depot
paths map through overlays to the same location in the repository,
-git-p4 can choose the wrong one. This is hard to solve without
-dedicating a client spec just for git-p4.
+'git p4' can choose the wrong one. This is hard to solve without
+dedicating a client spec just for 'git p4'.
-The name of the client can be given to git-p4 in multiple ways. The
+The name of the client can be given to 'git p4' in multiple ways. The
variable 'git-p4.client' takes precedence if it exists. Otherwise,
normal p4 mechanisms of determining the client are used: environment
variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.
git config --add git-p4.branchList main:branchB
-------------
+git-p4.ignoredP4Labels::
+ List of p4 labels to ignore. This is built automatically as
+ unimportable labels are discovered.
+
+git-p4.importLabels::
+ Import p4 labels into git, as per --import-labels.
+
+git-p4.labelImportRegexp::
+ Only p4 labels matching this regular expression will be imported. The
+ default value is '[a-zA-Z0-9_\-.]+$'.
+
git-p4.useClientSpec::
Specify that the p4 client spec should be used to identify p4
depot paths of interest. This is equivalent to specifying the
submission regardless.
git-p4.attemptRCSCleanup::
- If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
- ($Header$, etc). These would otherwise cause merge conflicts and prevent
- the submit going ahead. This option should be considered experimental at
- present.
+ If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
+ ($Header$, etc). These would otherwise cause merge conflicts and prevent
+ the submit going ahead. This option should be considered experimental at
+ present.
+
+git-p4.exportLabels::
+ Export git tags to p4 labels, as per --export-labels.
+
+git-p4.labelExportRegexp::
+ Only p4 labels matching this regular expression will be exported. The
+ default value is '[a-zA-Z0-9_\-.]+$'.
IMPLEMENTATION DETAILS
----------------------
is specified. This flag forces progress status even if the
standard error stream is not directed to a terminal.
---recurse-submodules=check::
- Check whether all submodule commits used by the revisions to be
- pushed are available on a remote tracking branch. Otherwise the
- push will be aborted and the command will exit with non-zero status.
+--recurse-submodules=check|on-demand::
+ Make sure all submodule commits used by the revisions to be
+ pushed are available on a remote tracking branch. If 'check' is
+ used git will verify that all submodule commits that changed in
+ the revisions to be pushed are available on at least one remote
+ of the submodule. If any commits are missing the push will be
+ aborted and exit with non-zero status. If 'on-demand' is used
+ all submodules that changed in the revisions to be pushed will
+ be pushed. If on-demand was not able to push all necessary
+ revisions it will also be aborted and exit with non-zero status.
include::urls-remotes.txt[]
will be reset to where it was when the rebase operation was
started.
+--keep-empty::
+ Keep the commits that do not change anything from its
+ parents in the result.
+
--skip::
Restart the rebasing process by skipping the current patch.
--git-dir::
Show `$GIT_DIR` if defined. Otherwise show the path to
- the .git directory, relative to the current directory.
+ the .git directory. The path shown, when relative, is
+ relative to the current working directory.
+
If `$GIT_DIR` is not defined and the current directory
is not detected to lie in a git repository or work tree
Terminate entries with NUL, instead of LF. This implies
the `--porcelain` output format if no other format is given.
+--column[=<options>]::
+--no-column::
+ Display untracked files in columns. See configuration variable
+ column.status for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never'
+ respectively.
+
OUTPUT
------
last fetched commit from the upstream SVN.
'dcommit'::
- Commit each diff from a specified head directly to the SVN
+ Commit each diff from the current branch directly to the SVN
repository, and then rebase or reset (depending on whether or
not there is a diff between SVN and head). This will create
a revision in SVN for each commit in git.
- It is recommended that you run 'git svn' fetch and rebase (not
- pull or merge) your commits against the latest changes in the
- SVN repository.
- An optional revision or branch argument may be specified, and
- causes 'git svn' to do all work on that revision/branch
- instead of HEAD.
- This is advantageous over 'set-tree' (below) because it produces
- cleaner, more linear history.
++
+When an optional git branch name (or a git commit object name)
+is specified as an argument, the subcommand works on the specified
+branch, not on the current branch.
++
+Use of 'dcommit' is preferred to 'set-tree' (below).
+
--no-rebase;;
After committing, do not rebase or reset.
--merge::
-s<strategy>::
--strategy=<strategy>::
+-p::
+--preserve-merges::
These are only used with the 'dcommit' and 'rebase' commands.
+
Passed directly to 'git rebase' when using 'dcommit' if a
REBASE VS. PULL/MERGE
---------------------
-
-Originally, 'git svn' recommended that the 'remotes/git-svn' branch be
-pulled or merged from. This is because the author favored
+Prefer to use 'git svn rebase' or 'git rebase', rather than
+'git pull' or 'git merge' to synchronize unintegrated commits with a 'git svn'
+branch. Doing so will keep the history of unintegrated commits linear with
+respect to the upstream SVN repository and allow the use of the preferred
+'git svn dcommit' subcommand to push unintegrated commits back into SVN.
+
+Originally, 'git svn' recommended that developers pulled or merged from
+the 'git svn' branch. This was because the author favored
`git svn set-tree B` to commit a single head rather than the
-`git svn set-tree A..B` notation to commit multiple commits.
-
-If you use `git svn set-tree A..B` to commit several diffs and you do
-not have the latest remotes/git-svn merged into my-branch, you should
-use `git svn rebase` to update your work branch instead of `git pull` or
-`git merge`. `pull`/`merge` can cause non-linear history to be flattened
-when committing into SVN, which can lead to merge commits reversing
-previous commits in SVN.
+`git svn set-tree A..B` notation to commit multiple commits. Use of
+'git pull' or 'git merge' with `git svn set-tree A..B` will cause non-linear
+history to be flattened when committing into SVN and this can lead to merge
+commits unexpectedly reversing previous commits in SVN.
MERGE TRACKING
--------------
<tagname> [<commit> | <object>]
'git tag' -d <tagname>...
'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+ [--column[=<options>] | --no-column] [<pattern>...]
[<pattern>...]
'git tag' -v <tagname>...
using fnmatch(3)). Multiple patterns may be given; if any of
them matches, the tag is shown.
+--column[=<options>]::
+--no-column::
+ Display tag listing in columns. See configuration variable
+ column.tag for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable when listing tags without annotation lines.
+
--contains <commit>::
Only list tags which contain the specified commit.
[--ignore-submodules]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
- [-z] [--stdin]
+ [-z] [--stdin] [--index-version <n>]
[--verbose]
[--] [<file>...]
--verbose::
Report what is being added and removed from index.
+--index-version <n>::
+ Write the resulting index out in the named on-disk format version.
+ The current default version is 2.
+
-z::
Only meaningful with `--stdin` or `--index-info`; paths are
separated with NUL character instead of LF.
`$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
--nofork`. The order of preference is the `$GIT_EDITOR`
environment variable, then `core.editor` configuration, then
- `$VISUAL`, then `$EDITOR`, and then finally 'vi'.
+ `$VISUAL`, then `$EDITOR`, and then the default chosen at compile
+ time, which is usually 'vi'.
+ifdef::git-default-editor[]
+ The build you are using chose '{git-default-editor}' as the default.
+endif::git-default-editor[]
GIT_PAGER::
Text viewer for use by git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference
is the `$GIT_PAGER` environment variable, then `core.pager`
- configuration, then `$PAGER`, and then finally 'less'.
+ configuration, then `$PAGER`, and then the default chosen at
+ compile time (usually 'less').
+ifdef::git-default-pager[]
+ The build you are using chose '{git-default-pager}' as the default.
+endif::git-default-pager[]
Diagnostics
-----------
----------------
Updating from ae3a2da... to a80b4aa....
Fast-forward (no commit created; -m option ignored)
- example | 1 +
- hello | 1 +
+ example | 1 +
+ hello | 1 +
2 files changed, 2 insertions(+)
----------------
Set `$maxload` to undefined value (`undef`) to turn this feature off.
The default value is 300.
+$omit_age_column::
+ If true, omit the column with date of the most current commit on the
+ projects list page. It can save a bit of I/O and a fork per repository.
+
+$omit_owner::
+ If true prevents displaying information about repository owner.
+
$per_request_config::
If this is set to code reference, it will be run once for each request.
You can set parts of configuration that change per session this way.
returning a `struct commit *` each time you call it. The end of the
revision list is indicated by returning a NULL pointer.
+`reset_revision_walk`::
+
+ Reset the flags used by the revision walking api. You can use
+ this to do multiple sequencial revision walks.
+
Data structures
---------------
are encoded in 7-bit ASCII and the encoding cannot contain a NUL
byte (iow, this is a UNIX pathname).
+ (Version 4) In version 4, the entry path name is prefix-compressed
+ relative to the path name for the previous entry (the very first
+ entry is encoded as if the path name for the previous entry is an
+ empty string). At the beginning of an entry, an integer N in the
+ variable width encoding (the same encoding as the offset is encoded
+ for OFS_DELTA pack entries; see pack-format.txt) is stored, followed
+ by a NUL-terminated string S. Removing N bytes from the end of the
+ path name for the previous entry, and replacing it with the string S
+ yields the path name for this entry.
+
1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes
while keeping the name NUL-terminated.
+ (Version 4) In version 4, the padding after the pathname does not
+ exist.
+
== Extensions
=== Cached tree
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.10.3
+DEF_VER=v1.7.10.GIT
LF='
'
use English. Under autoconf the configure script will do this
automatically if it can't find libintl on the system.
+ - Python version 2.6 or later is needed to use the git-p4
+ interface to Perforce.
+
- Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not
have all the libraries/tools needed, or you may have
# Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed
# programs as a tar, where bin/ and libexec/ might be on different file systems.
#
+# Define NO_INSTALL_HARDLINKS if you prefer to use either symbolic links or
+# copies to install built-in git commands e.g. git-cat-file.
+#
# Define USE_NED_ALLOCATOR if you want to replace the platforms default
# memory allocators with the nedmalloc allocator written by Niall Douglas.
#
# dependency rules.
#
# Define NATIVE_CRLF if your platform uses CRLF for line endings.
+#
+# Define XDL_FAST_HASH to use an alternative line-hashing method in
+# the diff algorithm. It gives a nice speedup if your processor has
+# fast unaligned word loads. Does NOT work on big-endian systems!
+# Enabled by default on x86_64.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
VCSSVN_H =
VCSSVN_OBJS =
VCSSVN_TEST_OBJS =
+MISC_H =
EXTRA_CPPFLAGS =
LIB_H =
LIB_OBJS =
SCRIPT_PERL += git-svn.perl
SCRIPT_PYTHON += git-remote-testgit.py
+SCRIPT_PYTHON += git-p4.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
+PROGRAM_OBJS += credential-store.o
PROGRAM_OBJS += daemon.o
PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += http-backend.o
PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAM_OBJS += shell.o
PROGRAM_OBJS += show-index.o
PROGRAM_OBJS += upload-pack.o
-PROGRAM_OBJS += http-backend.o
-PROGRAM_OBJS += sh-i18n--envsubst.o
-PROGRAM_OBJS += credential-store.o
# Binary suffix, set to .exe for Windows builds
X =
TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
-TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-revision-walking
TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-subprocess
XDIFF_LIB=xdiff/lib.a
VCSSVN_LIB=vcs-svn/lib.a
+XDIFF_H += xdiff/xinclude.h
+XDIFF_H += xdiff/xmacros.h
+XDIFF_H += xdiff/xdiff.h
+XDIFF_H += xdiff/xtypes.h
+XDIFF_H += xdiff/xutils.h
+XDIFF_H += xdiff/xprepare.h
+XDIFF_H += xdiff/xdiffi.h
+XDIFF_H += xdiff/xemit.h
+
+VCSSVN_H += vcs-svn/line_buffer.h
+VCSSVN_H += vcs-svn/sliding_window.h
+VCSSVN_H += vcs-svn/repo_tree.h
+VCSSVN_H += vcs-svn/fast_export.h
+VCSSVN_H += vcs-svn/svndiff.h
+VCSSVN_H += vcs-svn/svndump.h
+
+MISC_H += bisect.h
+MISC_H += branch.h
+MISC_H += bundle.h
+MISC_H += common-cmds.h
+MISC_H += fetch-pack.h
+MISC_H += reachable.h
+MISC_H += send-pack.h
+MISC_H += shortlog.h
+MISC_H += tar.h
+MISC_H += thread-utils.h
+MISC_H += url.h
+MISC_H += walker.h
+MISC_H += wt-status.h
+
LIB_H += advice.h
LIB_H += archive.h
LIB_H += argv-array.h
LIB_H += compat/mingw.h
LIB_H += compat/obstack.h
LIB_H += compat/terminal.h
+LIB_H += compat/win32/dirent.h
+LIB_H += compat/win32/poll.h
LIB_H += compat/win32/pthread.h
LIB_H += compat/win32/syslog.h
-LIB_H += compat/win32/poll.h
-LIB_H += compat/win32/dirent.h
LIB_H += connected.h
LIB_H += convert.h
LIB_H += credential.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
-LIB_H += diffcore.h
LIB_H += diff.h
+LIB_H += diffcore.h
LIB_H += dir.h
LIB_H += exec_cmd.h
LIB_H += fmt-merge-msg.h
LIB_H += mailmap.h
LIB_H += merge-file.h
LIB_H += merge-recursive.h
+LIB_H += mergesort.h
LIB_H += notes.h
LIB_H += notes-cache.h
LIB_H += notes-merge.h
LIB_H += unpack-trees.h
LIB_H += userdiff.h
LIB_H += utf8.h
+LIB_H += varint.h
LIB_H += xdiff-interface.h
LIB_H += xdiff/xdiff.h
LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o
LIB_OBJS += color.o
+LIB_OBJS += column.o
LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += environment.o
LIB_OBJS += exec_cmd.o
LIB_OBJS += fsck.o
-LIB_OBJS += gpg-interface.o
LIB_OBJS += gettext.o
+LIB_OBJS += gpg-interface.o
LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hash.o
LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o
LIB_OBJS += merge-recursive.o
+LIB_OBJS += mergesort.o
LIB_OBJS += name-hash.o
LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
LIB_OBJS += resolve-undo.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
+LIB_OBJS += sequencer.o
LIB_OBJS += server-info.o
LIB_OBJS += setup.o
-LIB_OBJS += sequencer.o
LIB_OBJS += sha1-array.o
LIB_OBJS += sha1-lookup.o
LIB_OBJS += sha1_file.o
LIB_OBJS += usage.o
LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
+LIB_OBJS += varint.o
LIB_OBJS += walker.o
LIB_OBJS += wrapper.o
LIB_OBJS += write_or_die.o
BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o
BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/column.o
BUILTIN_OBJS += builtin/commit-tree.o
BUILTIN_OBJS += builtin/commit.o
BUILTIN_OBJS += builtin/config.o
# because maintaining the nesting to match is a pain. If
# we had "elif" things would have been much nicer...
+ifeq ($(uname_M),x86_64)
+ XDL_FAST_HASH = YesPlease
+endif
ifeq ($(uname_S),OSF1)
# Need this for u_short definitions et al
BASIC_CFLAGS += -D_OSF_SOURCE
MSGFMT += --check --statistics
endif
+ifneq (,$(XDL_FAST_HASH))
+ BASIC_CFLAGS += -DXDL_FAST_HASH
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK=NoThanks
endif
export ASCIIDOC7
endif
+ifdef NO_INSTALL_HARDLINKS
+ export NO_INSTALL_HARDLINKS
+endif
+
### profile feedback build
#
BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
endif
+ifdef SHELL_PATH
+SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
+SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
+
+BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
+endif
+
ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS)
builtin/bundle.o bundle.o transport.o: bundle.h
builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
+builtin/index-pack.o builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
builtin/send-pack.o transport.o: send-pack.h
builtin/log.o builtin/shortlog.o: shortlog.h
builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
connect.o transport.o url.o http-backend.o: url.h
+builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
-XDIFF_H += xdiff/xinclude.h
-XDIFF_H += xdiff/xmacros.h
-XDIFF_H += xdiff/xdiff.h
-XDIFF_H += xdiff/xtypes.h
-XDIFF_H += xdiff/xutils.h
-XDIFF_H += xdiff/xprepare.h
-XDIFF_H += xdiff/xdiffi.h
-XDIFF_H += xdiff/xemit.h
-
xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
-VCSSVN_H += vcs-svn/line_buffer.h
-VCSSVN_H += vcs-svn/sliding_window.h
-VCSSVN_H += vcs-svn/repo_tree.h
-VCSSVN_H += vcs-svn/fast_export.h
-VCSSVN_H += vcs-svn/svndiff.h
-VCSSVN_H += vcs-svn/svndump.h
-
$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
endif
$(VCSSVN_LIB): $(VCSSVN_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
+export DEFAULT_EDITOR DEFAULT_PAGER
+
doc:
$(MAKE) -C Documentation all
--keyword=_ --keyword=N_ --keyword="Q_:1,2"
XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
-LOCALIZED_C := $(C_OBJ:o=c)
+LOCALIZED_C := $(C_OBJ:o=c) $(LIB_H) $(XDIFF_H) $(VCSSVN_H) $(MISC_H)
LOCALIZED_SH := $(SCRIPT_SH)
LOCALIZED_PERL := $(SCRIPT_PERL)
{ test "$$bindir/" = "$$execdir/" || \
for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
$(RM) "$$execdir/$$p" && \
- test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
+ test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
done; \
} && \
for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
$(RM) "$$bindir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
done && \
for p in $(BUILT_INS); do \
$(RM) "$$execdir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
for p in $$remote_curl_aliases; do \
$(RM) "$$execdir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
-Documentation/RelNotes/1.7.10.3.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.11.txt
\ No newline at end of file
#include "cache.h"
#include "tar.h"
#include "archive.h"
+#include "streaming.h"
#include "run-command.h"
#define RECORDSIZE (512)
* queues up writes, so that all our write(2) calls write exactly one
* full block; pads writes to RECORDSIZE
*/
-static void write_blocked(const void *data, unsigned long size)
+static void do_write_blocked(const void *data, unsigned long size)
{
const char *buf = data;
- unsigned long tail;
if (offset) {
unsigned long chunk = BLOCKSIZE - offset;
memcpy(block + offset, buf, size);
offset += size;
}
+}
+
+static void finish_record(void)
+{
+ unsigned long tail;
tail = offset % RECORDSIZE;
if (tail) {
memset(block + offset, 0, RECORDSIZE - tail);
write_if_needed();
}
+static void write_blocked(const void *data, unsigned long size)
+{
+ do_write_blocked(data, size);
+ finish_record();
+}
+
/*
* The end of tar archives is marked by 2*512 nul bytes and after that
* follows the rest of the block (if any).
}
}
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static int stream_blocked(const unsigned char *sha1)
+{
+ struct git_istream *st;
+ enum object_type type;
+ unsigned long sz;
+ char buf[BLOCKSIZE];
+ ssize_t readlen;
+
+ st = open_istream(sha1, &type, &sz, NULL);
+ if (!st)
+ return error("cannot stream blob %s", sha1_to_hex(sha1));
+ for (;;) {
+ readlen = read_istream(st, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ do_write_blocked(buf, readlen);
+ }
+ close_istream(st);
+ if (!readlen)
+ finish_record();
+ return readlen;
+}
+
/*
* pax extended header records have the format "%u %s=%s\n". %u contains
* the size of the whole string (including the %u), the first %s is the
static unsigned int ustar_header_chksum(const struct ustar_header *header)
{
- char *p = (char *)header;
+ const char *p = (const char *)header;
unsigned int chksum = 0;
while (p < header->chksum)
chksum += *p++;
chksum += sizeof(header->chksum) * ' ';
p += sizeof(header->chksum);
- while (p < (char *)header + sizeof(struct ustar_header))
+ while (p < (const char *)header + sizeof(struct ustar_header))
chksum += *p++;
return chksum;
}
return i;
}
+static void prepare_header(struct archiver_args *args,
+ struct ustar_header *header,
+ unsigned int mode, unsigned long size)
+{
+ sprintf(header->mode, "%07o", mode & 07777);
+ sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0);
+ sprintf(header->mtime, "%011lo", (unsigned long) args->time);
+
+ sprintf(header->uid, "%07o", 0);
+ sprintf(header->gid, "%07o", 0);
+ strlcpy(header->uname, "root", sizeof(header->uname));
+ strlcpy(header->gname, "root", sizeof(header->gname));
+ sprintf(header->devmajor, "%07o", 0);
+ sprintf(header->devminor, "%07o", 0);
+
+ memcpy(header->magic, "ustar", 6);
+ memcpy(header->version, "00", 2);
+
+ sprintf(header->chksum, "%07o", ustar_header_chksum(header));
+}
+
+static int write_extended_header(struct archiver_args *args,
+ const unsigned char *sha1,
+ const void *buffer, unsigned long size)
+{
+ struct ustar_header header;
+ unsigned int mode;
+ memset(&header, 0, sizeof(header));
+ *header.typeflag = TYPEFLAG_EXT_HEADER;
+ mode = 0100666;
+ sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+ prepare_header(args, &header, mode, size);
+ write_blocked(&header, sizeof(header));
+ write_blocked(buffer, size);
+ return 0;
+}
+
static int write_tar_entry(struct archiver_args *args,
- const unsigned char *sha1, const char *path, size_t pathlen,
- unsigned int mode, void *buffer, unsigned long size)
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode)
{
struct ustar_header header;
struct strbuf ext_header = STRBUF_INIT;
+ unsigned int old_mode = mode;
+ unsigned long size;
+ void *buffer;
int err = 0;
memset(&header, 0, sizeof(header));
- if (!sha1) {
- *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
- mode = 0100666;
- strcpy(header.name, "pax_global_header");
- } else if (!path) {
- *header.typeflag = TYPEFLAG_EXT_HEADER;
- mode = 0100666;
- sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
+ *header.typeflag = TYPEFLAG_DIR;
+ mode = (mode | 0777) & ~tar_umask;
+ } else if (S_ISLNK(mode)) {
+ *header.typeflag = TYPEFLAG_LNK;
+ mode |= 0777;
+ } else if (S_ISREG(mode)) {
+ *header.typeflag = TYPEFLAG_REG;
+ mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
} else {
- if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
- *header.typeflag = TYPEFLAG_DIR;
- mode = (mode | 0777) & ~tar_umask;
- } else if (S_ISLNK(mode)) {
- *header.typeflag = TYPEFLAG_LNK;
- mode |= 0777;
- } else if (S_ISREG(mode)) {
- *header.typeflag = TYPEFLAG_REG;
- mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
+ return error("unsupported file mode: 0%o (SHA1: %s)",
+ mode, sha1_to_hex(sha1));
+ }
+ if (pathlen > sizeof(header.name)) {
+ size_t plen = get_path_prefix(path, pathlen,
+ sizeof(header.prefix));
+ size_t rest = pathlen - plen - 1;
+ if (plen > 0 && rest <= sizeof(header.name)) {
+ memcpy(header.prefix, path, plen);
+ memcpy(header.name, path + plen + 1, rest);
} else {
- return error("unsupported file mode: 0%o (SHA1: %s)",
- mode, sha1_to_hex(sha1));
+ sprintf(header.name, "%s.data",
+ sha1_to_hex(sha1));
+ strbuf_append_ext_header(&ext_header, "path",
+ path, pathlen);
}
- if (pathlen > sizeof(header.name)) {
- size_t plen = get_path_prefix(path, pathlen,
- sizeof(header.prefix));
- size_t rest = pathlen - plen - 1;
- if (plen > 0 && rest <= sizeof(header.name)) {
- memcpy(header.prefix, path, plen);
- memcpy(header.name, path + plen + 1, rest);
- } else {
- sprintf(header.name, "%s.data",
- sha1_to_hex(sha1));
- strbuf_append_ext_header(&ext_header, "path",
- path, pathlen);
- }
- } else
- memcpy(header.name, path, pathlen);
+ } else
+ memcpy(header.name, path, pathlen);
+
+ if (S_ISREG(mode) && !args->convert &&
+ sha1_object_info(sha1, &size) == OBJ_BLOB &&
+ size > big_file_threshold)
+ buffer = NULL;
+ else if (S_ISLNK(mode) || S_ISREG(mode)) {
+ enum object_type type;
+ buffer = sha1_file_to_archive(args, path, sha1, old_mode, &type, &size);
+ if (!buffer)
+ return error("cannot read %s", sha1_to_hex(sha1));
+ } else {
+ buffer = NULL;
+ size = 0;
}
- if (S_ISLNK(mode) && buffer) {
+ if (S_ISLNK(mode)) {
if (size > sizeof(header.linkname)) {
sprintf(header.linkname, "see %s.paxheader",
sha1_to_hex(sha1));
memcpy(header.linkname, buffer, size);
}
- sprintf(header.mode, "%07o", mode & 07777);
- sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
- sprintf(header.mtime, "%011lo", (unsigned long) args->time);
-
- sprintf(header.uid, "%07o", 0);
- sprintf(header.gid, "%07o", 0);
- strlcpy(header.uname, "root", sizeof(header.uname));
- strlcpy(header.gname, "root", sizeof(header.gname));
- sprintf(header.devmajor, "%07o", 0);
- sprintf(header.devminor, "%07o", 0);
-
- memcpy(header.magic, "ustar", 6);
- memcpy(header.version, "00", 2);
-
- sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+ prepare_header(args, &header, mode, size);
if (ext_header.len > 0) {
- err = write_tar_entry(args, sha1, NULL, 0, 0, ext_header.buf,
- ext_header.len);
- if (err)
+ err = write_extended_header(args, sha1, ext_header.buf,
+ ext_header.len);
+ if (err) {
+ free(buffer);
return err;
+ }
}
strbuf_release(&ext_header);
write_blocked(&header, sizeof(header));
- if (S_ISREG(mode) && buffer && size > 0)
- write_blocked(buffer, size);
+ if (S_ISREG(mode) && size > 0) {
+ if (buffer)
+ write_blocked(buffer, size);
+ else
+ err = stream_blocked(sha1);
+ }
+ free(buffer);
return err;
}
{
const unsigned char *sha1 = args->commit_sha1;
struct strbuf ext_header = STRBUF_INIT;
- int err;
+ struct ustar_header header;
+ unsigned int mode;
+ int err = 0;
strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
- err = write_tar_entry(args, NULL, NULL, 0, 0, ext_header.buf,
- ext_header.len);
+ memset(&header, 0, sizeof(header));
+ *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+ mode = 0100666;
+ strcpy(header.name, "pax_global_header");
+ prepare_header(args, &header, mode, ext_header.len);
+ write_blocked(&header, sizeof(header));
+ write_blocked(ext_header.buf, ext_header.len);
strbuf_release(&ext_header);
return err;
}
*/
#include "cache.h"
#include "archive.h"
+#include "streaming.h"
static int zip_date;
static int zip_time;
static unsigned int zip_dir_entries;
#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
+#define ZIP_STREAM (8)
struct zip_local_header {
unsigned char magic[4];
unsigned char _end[1];
};
+struct zip_data_desc {
+ unsigned char magic[4];
+ unsigned char crc32[4];
+ unsigned char compressed_size[4];
+ unsigned char size[4];
+ unsigned char _end[1];
+};
+
struct zip_dir_header {
unsigned char magic[4];
unsigned char creator_version[2];
* we're interested in.
*/
#define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end)
+#define ZIP_DATA_DESC_SIZE offsetof(struct zip_data_desc, _end)
#define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end)
#define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end)
return buffer;
}
+static void write_zip_data_desc(unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ struct zip_data_desc trailer;
+
+ copy_le32(trailer.magic, 0x08074b50);
+ copy_le32(trailer.crc32, crc);
+ copy_le32(trailer.compressed_size, compressed_size);
+ copy_le32(trailer.size, size);
+ write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE);
+}
+
+static void set_zip_dir_data_desc(struct zip_dir_header *header,
+ unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ copy_le32(header->crc32, crc);
+ copy_le32(header->compressed_size, compressed_size);
+ copy_le32(header->size, size);
+}
+
+static void set_zip_header_data_desc(struct zip_local_header *header,
+ unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ copy_le32(header->crc32, crc);
+ copy_le32(header->compressed_size, compressed_size);
+ copy_le32(header->size, size);
+}
+
+#define STREAM_BUFFER_SIZE (1024 * 16)
+
static int write_zip_entry(struct archiver_args *args,
- const unsigned char *sha1, const char *path, size_t pathlen,
- unsigned int mode, void *buffer, unsigned long size)
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode)
{
struct zip_local_header header;
struct zip_dir_header dirent;
unsigned long attr2;
unsigned long compressed_size;
- unsigned long uncompressed_size;
unsigned long crc;
unsigned long direntsize;
int method;
unsigned char *out;
void *deflated = NULL;
+ void *buffer;
+ struct git_istream *stream = NULL;
+ unsigned long flags = 0;
+ unsigned long size;
crc = crc32(0, NULL, 0);
method = 0;
attr2 = 16;
out = NULL;
- uncompressed_size = 0;
+ size = 0;
compressed_size = 0;
+ buffer = NULL;
+ size = 0;
} else if (S_ISREG(mode) || S_ISLNK(mode)) {
+ enum object_type type = sha1_object_info(sha1, &size);
+
method = 0;
attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
(mode & 0111) ? ((mode) << 16) : 0;
- if (S_ISREG(mode) && args->compression_level != 0)
+ if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
method = 8;
- crc = crc32(crc, buffer, size);
- out = buffer;
- uncompressed_size = size;
compressed_size = size;
+
+ if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
+ size > big_file_threshold) {
+ stream = open_istream(sha1, &type, &size, NULL);
+ if (!stream)
+ return error("cannot stream blob %s",
+ sha1_to_hex(sha1));
+ flags |= ZIP_STREAM;
+ out = buffer = NULL;
+ } else {
+ buffer = sha1_file_to_archive(args, path, sha1, mode,
+ &type, &size);
+ if (!buffer)
+ return error("cannot read %s",
+ sha1_to_hex(sha1));
+ crc = crc32(crc, buffer, size);
+ out = buffer;
+ }
} else {
return error("unsupported file mode: 0%o (SHA1: %s)", mode,
sha1_to_hex(sha1));
}
- if (method == 8) {
+ if (buffer && method == 8) {
deflated = zlib_deflate(buffer, size, args->compression_level,
&compressed_size);
if (deflated && compressed_size - 6 < size) {
copy_le16(dirent.creator_version,
S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
copy_le16(dirent.version, 10);
- copy_le16(dirent.flags, 0);
+ copy_le16(dirent.flags, flags);
copy_le16(dirent.compression_method, method);
copy_le16(dirent.mtime, zip_time);
copy_le16(dirent.mdate, zip_date);
- copy_le32(dirent.crc32, crc);
- copy_le32(dirent.compressed_size, compressed_size);
- copy_le32(dirent.size, uncompressed_size);
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
copy_le16(dirent.filename_length, pathlen);
copy_le16(dirent.extra_length, 0);
copy_le16(dirent.comment_length, 0);
copy_le16(dirent.attr1, 0);
copy_le32(dirent.attr2, attr2);
copy_le32(dirent.offset, zip_offset);
- memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
- zip_dir_offset += ZIP_DIR_HEADER_SIZE;
- memcpy(zip_dir + zip_dir_offset, path, pathlen);
- zip_dir_offset += pathlen;
- zip_dir_entries++;
copy_le32(header.magic, 0x04034b50);
copy_le16(header.version, 10);
- copy_le16(header.flags, 0);
+ copy_le16(header.flags, flags);
copy_le16(header.compression_method, method);
copy_le16(header.mtime, zip_time);
copy_le16(header.mdate, zip_date);
- copy_le32(header.crc32, crc);
- copy_le32(header.compressed_size, compressed_size);
- copy_le32(header.size, uncompressed_size);
+ if (flags & ZIP_STREAM)
+ set_zip_header_data_desc(&header, 0, 0, 0);
+ else
+ set_zip_header_data_desc(&header, size, compressed_size, crc);
copy_le16(header.filename_length, pathlen);
copy_le16(header.extra_length, 0);
write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
zip_offset += ZIP_LOCAL_HEADER_SIZE;
write_or_die(1, path, pathlen);
zip_offset += pathlen;
- if (compressed_size > 0) {
+ if (stream && method == 0) {
+ unsigned char buf[STREAM_BUFFER_SIZE];
+ ssize_t readlen;
+
+ for (;;) {
+ readlen = read_istream(stream, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ crc = crc32(crc, buf, readlen);
+ write_or_die(1, buf, readlen);
+ }
+ close_istream(stream);
+ if (readlen)
+ return readlen;
+
+ compressed_size = size;
+ zip_offset += compressed_size;
+
+ write_zip_data_desc(size, compressed_size, crc);
+ zip_offset += ZIP_DATA_DESC_SIZE;
+
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+ } else if (stream && method == 8) {
+ unsigned char buf[STREAM_BUFFER_SIZE];
+ ssize_t readlen;
+ git_zstream zstream;
+ int result;
+ size_t out_len;
+ unsigned char compressed[STREAM_BUFFER_SIZE * 2];
+
+ memset(&zstream, 0, sizeof(zstream));
+ git_deflate_init(&zstream, args->compression_level);
+
+ compressed_size = 0;
+ zstream.next_out = compressed;
+ zstream.avail_out = sizeof(compressed);
+
+ for (;;) {
+ readlen = read_istream(stream, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ crc = crc32(crc, buf, readlen);
+
+ zstream.next_in = buf;
+ zstream.avail_in = readlen;
+ result = git_deflate(&zstream, 0);
+ if (result != Z_OK)
+ die("deflate error (%d)", result);
+ out = compressed;
+ if (!compressed_size)
+ out += 2;
+ out_len = zstream.next_out - out;
+
+ if (out_len > 0) {
+ write_or_die(1, out, out_len);
+ compressed_size += out_len;
+ zstream.next_out = compressed;
+ zstream.avail_out = sizeof(compressed);
+ }
+
+ }
+ close_istream(stream);
+ if (readlen)
+ return readlen;
+
+ zstream.next_in = buf;
+ zstream.avail_in = 0;
+ result = git_deflate(&zstream, Z_FINISH);
+ if (result != Z_STREAM_END)
+ die("deflate error (%d)", result);
+
+ git_deflate_end(&zstream);
+ out = compressed;
+ if (!compressed_size)
+ out += 2;
+ out_len = zstream.next_out - out - 4;
+ write_or_die(1, out, out_len);
+ compressed_size += out_len;
+ zip_offset += compressed_size;
+
+ write_zip_data_desc(size, compressed_size, crc);
+ zip_offset += ZIP_DATA_DESC_SIZE;
+
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+ } else if (compressed_size > 0) {
write_or_die(1, out, compressed_size);
zip_offset += compressed_size;
}
free(deflated);
+ free(buffer);
+
+ memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+ zip_dir_offset += ZIP_DIR_HEADER_SIZE;
+ memcpy(zip_dir + zip_dir_offset, path, pathlen);
+ zip_dir_offset += pathlen;
+ zip_dir_entries++;
return 0;
}
free(to_free);
}
-static void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
- unsigned int mode, enum object_type *type,
- unsigned long *sizep, const struct commit *commit)
+void *sha1_file_to_archive(const struct archiver_args *args,
+ const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep)
{
void *buffer;
+ const struct commit *commit = args->convert ? args->commit : NULL;
+ path += args->baselen;
buffer = read_sha1_file(sha1, type, sizep);
if (buffer && S_ISREG(mode)) {
struct strbuf buf = STRBUF_INIT;
write_archive_entry_fn_t write_entry = c->write_entry;
struct git_attr_check check[2];
const char *path_without_prefix;
- int convert = 0;
int err;
- enum object_type type;
- unsigned long size;
- void *buffer;
+ args->convert = 0;
strbuf_reset(&path);
strbuf_grow(&path, PATH_MAX);
strbuf_add(&path, args->base, args->baselen);
if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
if (ATTR_TRUE(check[0].value))
return 0;
- convert = ATTR_TRUE(check[1].value);
+ args->convert = ATTR_TRUE(check[1].value);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
strbuf_addch(&path, '/');
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
+ err = write_entry(args, sha1, path.buf, path.len, mode);
if (err)
return err;
return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
}
- buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
- &type, &size, convert ? args->commit : NULL);
- if (!buffer)
- return error("cannot read %s", sha1_to_hex(sha1));
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- err = write_entry(args, sha1, path.buf, path.len, mode, buffer, size);
- free(buffer);
- return err;
+ return write_entry(args, sha1, path.buf, path.len, mode);
}
int write_archive_entries(struct archiver_args *args,
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)len, args->base);
err = write_entry(args, args->tree->object.sha1, args->base,
- len, 040777, NULL, 0);
+ len, 040777);
if (err)
return err;
}
/* Remotes are only allowed to fetch actual refs */
if (remote) {
char *ref = NULL;
- const char *refname, *colon = NULL;
-
- colon = strchr(name, ':');
- if (colon)
- refname = xstrndup(name, colon - name);
- else
- refname = name;
-
- if (!dwim_ref(refname, strlen(refname), sha1, &ref))
- die("no such ref: %s", refname);
- if (refname != name)
- free((void *)refname);
+ const char *colon = strchr(name, ':');
+ int refnamelen = colon ? colon - name : strlen(name);
+
+ if (!dwim_ref(name, refnamelen, sha1, &ref))
+ die("no such ref: %.*s", refnamelen, name);
free(ref);
}
const char **pathspec;
unsigned int verbose : 1;
unsigned int worktree_attributes : 1;
+ unsigned int convert : 1;
int compression_level;
};
extern void init_tar_archiver(void);
extern void init_zip_archiver(void);
-typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
+typedef int (*write_archive_entry_fn_t)(struct archiver_args *args,
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode);
extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry);
extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote);
const char *archive_format_from_filename(const char *filename);
+extern void *sha1_file_to_archive(const struct archiver_args *args,
+ const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep);
#endif /* ARCHIVE_H */
*/
static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
{
- const char *filename = git_path("BISECT_ANCESTORS_OK");
+ char *filename = xstrdup(git_path("BISECT_ANCESTORS_OK"));
struct stat st;
int fd;
/* Check if file BISECT_ANCESTORS_OK exists. */
if (!stat(filename, &st) && S_ISREG(st.st_mode))
- return;
+ goto done;
/* Bisecting with no good rev is ok. */
if (good_revs.nr == 0)
- return;
+ goto done;
/* Check if all good revs are ancestor of the bad rev. */
if (check_ancestors(prefix))
filename, strerror(errno));
else
close(fd);
+ done:
+ free(filename);
}
/*
* config.
*/
static int setup_tracking(const char *new_ref, const char *orig_ref,
- enum branch_track track)
+ enum branch_track track, int quiet)
{
struct tracking tracking;
+ int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
if (strlen(new_ref) > 1024 - 7 - 7 - 1)
return error("Tracking not set up: name too long: %s",
return error("Not tracking: ambiguous information for ref %s",
orig_ref);
- install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote,
+ install_branch_config(config_flags, new_ref, tracking.remote,
tracking.src ? tracking.src : orig_ref);
free(tracking.src);
void create_branch(const char *head,
const char *name, const char *start_name,
int force, int reflog, int clobber_head,
- enum branch_track track)
+ int quiet, enum branch_track track)
{
struct ref_lock *lock = NULL;
struct commit *commit;
start_name);
if (real_ref && track)
- setup_tracking(ref.buf+11, real_ref, track);
+ setup_tracking(ref.buf+11, real_ref, track, quiet);
if (!dont_change_ref)
if (write_ref_sha1(lock, sha1, msg) < 0)
*/
void create_branch(const char *head, const char *name, const char *start_name,
int force, int reflog,
- int clobber_head, enum branch_track track);
+ int clobber_head, int quiet, enum branch_track track);
/*
* Validates that the requested branch may be created, returning the
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
extern int cmd_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix);
+extern int cmd_column(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix);
static int line_termination = '\n';
static unsigned int p_context = UINT_MAX;
static const char * const apply_usage[] = {
- "git apply [options] [<patch>...]",
+ N_("git apply [options] [<patch>...]"),
NULL
};
ws_error_action = correct_ws_error;
return;
}
- die("unrecognized whitespace option '%s'", option);
+ die(_("unrecognized whitespace option '%s'"), option);
}
static void parse_ignorewhitespace_option(const char *option)
ws_ignore_action = ignore_ws_change;
return;
}
- die("unrecognized whitespace ignore option '%s'", option);
+ die(_("unrecognized whitespace ignore option '%s'"), option);
}
static void set_default_whitespace_mode(const char *whitespace_option)
unsigned long leading, trailing;
unsigned long oldpos, oldlines;
unsigned long newpos, newlines;
+ /*
+ * 'patch' is usually borrowed from buf in apply_patch(),
+ * but some codepaths store an allocated buffer.
+ */
const char *patch;
+ unsigned free_patch:1,
+ rejected:1;
int size;
- int rejected;
int linenr;
struct fragment *next;
};
struct patch *next;
};
+static void free_fragment_list(struct fragment *list)
+{
+ while (list) {
+ struct fragment *next = list->next;
+ if (list->free_patch)
+ free((char *)list->patch);
+ free(list);
+ list = next;
+ }
+}
+
+static void free_patch(struct patch *patch)
+{
+ free_fragment_list(patch->fragments);
+ free(patch->def_name);
+ free(patch->old_name);
+ free(patch->new_name);
+ free(patch->result);
+ free(patch);
+}
+
+static void free_patch_list(struct patch *list)
+{
+ while (list) {
+ struct patch *next = list->next;
+ free_patch(list);
+ list = next;
+ }
+}
+
/*
* A line in a file, len-bytes long (includes the terminating LF,
* except for an incomplete line at the end if the file ends with
img->nr++;
}
+/*
+ * "buf" has the file contents to be patched (read from various sources).
+ * attach it to "image" and add line-based index to it.
+ * "image" now owns the "buf".
+ */
static void prepare_image(struct image *image, char *buf, size_t len,
int prepare_linetable)
{
image->len = 0;
}
-static void say_patch_name(FILE *output, const char *pre,
- struct patch *patch, const char *post)
+/* fmt must contain _one_ %s and no other substitution */
+static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
{
- fputs(pre, output);
+ struct strbuf sb = STRBUF_INIT;
+
if (patch->old_name && patch->new_name &&
strcmp(patch->old_name, patch->new_name)) {
- quote_c_style(patch->old_name, NULL, output, 0);
- fputs(" => ", output);
- quote_c_style(patch->new_name, NULL, output, 0);
+ quote_c_style(patch->old_name, &sb, NULL, 0);
+ strbuf_addstr(&sb, " => ");
+ quote_c_style(patch->new_name, &sb, NULL, 0);
} else {
const char *n = patch->new_name;
if (!n)
n = patch->old_name;
- quote_c_style(n, NULL, output, 0);
+ quote_c_style(n, &sb, NULL, 0);
}
- fputs(post, output);
+ fprintf(output, fmt, sb.buf);
+ fputc('\n', output);
+ strbuf_release(&sb);
}
-#define CHUNKSIZE (8192)
#define SLOP (16)
static void read_patch_file(struct strbuf *sb, int fd)
return name;
}
-static char *find_name_gnu(const char *line, char *def, int p_value)
+static char *find_name_gnu(const char *line, const char *def, int p_value)
{
struct strbuf name = STRBUF_INIT;
char *cp;
cp++;
}
- /* name can later be freed, so we need
- * to memmove, not just return cp
- */
strbuf_remove(&name, 0, cp - name.buf);
- free(def);
if (root)
strbuf_insert(&name, 0, root, root_len);
return squash_slash(strbuf_detach(&name, NULL));
return line + len - end;
}
-static char *find_name_common(const char *line, char *def, int p_value,
- const char *end, int terminate)
+static char *null_strdup(const char *s)
+{
+ return s ? xstrdup(s) : NULL;
+}
+
+static char *find_name_common(const char *line, const char *def,
+ int p_value, const char *end, int terminate)
{
int len;
const char *start = NULL;
start = line;
}
if (!start)
- return squash_slash(def);
+ return squash_slash(null_strdup(def));
len = line - start;
if (!len)
- return squash_slash(def);
+ return squash_slash(null_strdup(def));
/*
* Generally we prefer the shorter name, especially
if (def) {
int deflen = strlen(def);
if (deflen < len && !strncmp(start, def, deflen))
- return squash_slash(def);
- free(def);
+ return squash_slash(xstrdup(def));
}
if (root) {
if (!stamp) {
stamp = xmalloc(sizeof(*stamp));
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
- warning("Cannot prepare timestamp regexp %s",
+ warning(_("Cannot prepare timestamp regexp %s"),
stamp_regexp);
return 0;
}
status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
if (status) {
if (status != REG_NOMATCH)
- warning("regexec returned %d for input: %s",
+ warning(_("regexec returned %d for input: %s"),
status, timestamp);
return 0;
}
name = find_name_traditional(first, NULL, p_value);
patch->old_name = name;
} else {
- name = find_name_traditional(first, NULL, p_value);
- name = find_name_traditional(second, name, p_value);
+ char *first_name;
+ first_name = find_name_traditional(first, NULL, p_value);
+ name = find_name_traditional(second, first_name, p_value);
+ free(first_name);
if (has_epoch_timestamp(first)) {
patch->is_new = 1;
patch->is_delete = 0;
patch->is_delete = 1;
patch->old_name = name;
} else {
- patch->old_name = patch->new_name = name;
+ patch->old_name = name;
+ patch->new_name = xstrdup(name);
}
}
if (!name)
- die("unable to find filename in patch at line %d", linenr);
+ die(_("unable to find filename in patch at line %d"), linenr);
}
static int gitdiff_hdrend(const char *line, struct patch *patch)
* their names against any previous information, just
* to make sure..
*/
-static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+#define DIFF_OLD_NAME 0
+#define DIFF_NEW_NAME 1
+
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side)
{
if (!orig_name && !isnull)
return find_name(line, NULL, p_value, TERM_TAB);
name = orig_name;
len = strlen(name);
if (isnull)
- die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+ die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
another = find_name(line, NULL, p_value, TERM_TAB);
if (!another || memcmp(another, name, len + 1))
- die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+ die((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
free(another);
return orig_name;
}
else {
/* expect "/dev/null" */
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
- die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
+ die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
return NULL;
}
}
static int gitdiff_oldname(const char *line, struct patch *patch)
{
- patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+ char *orig = patch->old_name;
+ patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
+ DIFF_OLD_NAME);
+ if (orig != patch->old_name)
+ free(orig);
return 0;
}
static int gitdiff_newname(const char *line, struct patch *patch)
{
- patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+ char *orig = patch->new_name;
+ patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
+ DIFF_NEW_NAME);
+ if (orig != patch->new_name)
+ free(orig);
return 0;
}
static int gitdiff_delete(const char *line, struct patch *patch)
{
patch->is_delete = 1;
- patch->old_name = patch->def_name;
+ free(patch->old_name);
+ patch->old_name = null_strdup(patch->def_name);
return gitdiff_oldmode(line, patch);
}
static int gitdiff_newfile(const char *line, struct patch *patch)
{
patch->is_new = 1;
- patch->new_name = patch->def_name;
+ free(patch->new_name);
+ patch->new_name = null_strdup(patch->def_name);
return gitdiff_newmode(line, patch);
}
static int gitdiff_copysrc(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_copydst(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamesrc(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamedst(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
* creation or deletion of an empty file. In any of these cases,
* both sides are the same name under a/ and b/ respectively.
*/
-static char *git_header_name(char *line, int llen)
+static char *git_header_name(const char *line, int llen)
{
const char *name;
const char *second = NULL;
}
/* Verify that we recognize the lines following a git header */
-static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
{
unsigned long offset;
return offset + ex;
}
-static void recount_diff(char *line, int size, struct fragment *fragment)
+static void recount_diff(const char *line, int size, struct fragment *fragment)
{
int oldlines = 0, newlines = 0, ret = 0;
break;
}
if (ret) {
- warning("recount: unexpected line: %.*s",
+ warning(_("recount: unexpected line: %.*s"),
(int)linelen(line, size), line);
return;
}
* Parse a unified diff fragment header of the
* form "@@ -a,b +c,d @@"
*/
-static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
{
int offset;
return offset;
}
-static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
{
unsigned long offset, len;
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- die("patch fragment without header at line %d: %.*s",
+ die(_("patch fragment without header at line %d: %.*s"),
linenr, (int)len-1, line);
}
continue;
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name)
- die("git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)" , p_value, linenr);
- patch->old_name = patch->new_name = patch->def_name;
+ die(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ p_value),
+ p_value, linenr);
+ patch->old_name = xstrdup(patch->def_name);
+ patch->new_name = xstrdup(patch->def_name);
}
if (!patch->is_delete && !patch->new_name)
die("git diff header lacks filename information "
* between a "---" that is part of a patch, and a "---" that starts
* the next patch is to look at the line counts..
*/
-static int parse_fragment(char *line, unsigned long size,
+static int parse_fragment(const char *line, unsigned long size,
struct patch *patch, struct fragment *fragment)
{
int added, deleted;
patch->lines_deleted += deleted;
if (0 < patch->is_new && oldlines)
- return error("new file depends on old contents");
+ return error(_("new file depends on old contents"));
if (0 < patch->is_delete && newlines)
- return error("deleted file still has contents");
+ return error(_("deleted file still has contents"));
return offset;
}
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+/*
+ * We have seen "diff --git a/... b/..." header (or a traditional patch
+ * header). Read hunks that belong to this patch into fragments and hang
+ * them to the given patch structure.
+ *
+ * The (fragment->patch, fragment->size) pair points into the memory given
+ * by the caller, not a copy, when we return.
+ */
+static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
unsigned long oldlines = 0, newlines = 0, context = 0;
fragment->linenr = linenr;
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
- die("corrupt patch at line %d", linenr);
+ die(_("corrupt patch at line %d"), linenr);
fragment->patch = line;
fragment->size = len;
oldlines += fragment->oldlines;
patch->is_delete = 0;
if (0 < patch->is_new && oldlines)
- die("new file %s depends on old contents", patch->new_name);
+ die(_("new file %s depends on old contents"), patch->new_name);
if (0 < patch->is_delete && newlines)
- die("deleted file %s still has contents", patch->old_name);
+ die(_("deleted file %s still has contents"), patch->old_name);
if (!patch->is_delete && !newlines && context)
- fprintf(stderr, "** warning: file %s becomes empty but "
- "is not deleted\n", patch->new_name);
+ fprintf_ln(stderr,
+ _("** warning: "
+ "file %s becomes empty but is not deleted"),
+ patch->new_name);
return offset;
}
return out;
}
+/*
+ * Read a binary hunk and return a new fragment; fragment->patch
+ * points at an allocated memory that the caller must free, so
+ * it is marked as "->free_patch = 1".
+ */
static struct fragment *parse_binary_hunk(char **buf_p,
unsigned long *sz_p,
int *status_p,
frag = xcalloc(1, sizeof(*frag));
frag->patch = inflate_it(data, hunk_size, origlen);
+ frag->free_patch = 1;
if (!frag->patch)
goto corrupt;
free(data);
corrupt:
free(data);
*status_p = -1;
- error("corrupt binary patch at line %d: %.*s",
+ error(_("corrupt binary patch at line %d: %.*s"),
linenr-1, llen-1, buffer);
return NULL;
}
forward = parse_binary_hunk(&buffer, &size, &status, &used);
if (!forward && !status)
/* there has to be one hunk (forward hunk) */
- return error("unrecognized binary patch at line %d", linenr-1);
+ return error(_("unrecognized binary patch at line %d"), linenr-1);
if (status)
/* otherwise we already gave an error message */
return status;
return used;
}
+/*
+ * Read the patch text in "buffer" taht extends for "size" bytes; stop
+ * reading after seeing a single patch (i.e. changes to a single file).
+ * Create fragments (i.e. patch hunks) and hang them to the given patch.
+ * Return the number of bytes consumed, so that the caller can call us
+ * again for the next patch.
+ */
static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
{
int hdrsize, patchsize;
*/
if ((apply || check) &&
(!patch->is_binary && !metadata_changes(patch)))
- die("patch with only garbage at line %d", linenr);
+ die(_("patch with only garbage at line %d"), linenr);
}
return offset + hdrsize + patchsize;
switch (st->st_mode & S_IFMT) {
case S_IFLNK:
if (strbuf_readlink(buf, path, st->st_size) < 0)
- return error("unable to read symlink %s", path);
+ return error(_("unable to read symlink %s"), path);
return 0;
case S_IFREG:
if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
- return error("unable to open or read %s", path);
+ return error(_("unable to open or read %s"), path);
convert_to_git(path, buf->buf, buf->len, buf, 0);
return 0;
default:
ctx++;
}
if (preimage->nr <= ctx)
- die("oops");
+ die(_("oops"));
/* and copy it in, while fixing the line length */
len = preimage->line[ctx].len;
img->len -= img->line[--img->nr].len;
}
+/*
+ * The change from "preimage" and "postimage" has been found to
+ * apply at applied_pos (counts in line numbers) in "img".
+ * Update "img" to remove "preimage" and replace it with "postimage".
+ */
static void update_image(struct image *img,
int applied_pos,
struct image *preimage,
img->nr = nr;
}
+/*
+ * Use the patch-hunk text in "frag" to prepare two images (preimage and
+ * postimage) for the hunk. Find lines that match "preimage" in "img" and
+ * replace the part of "img" with "postimage" text.
+ */
static int apply_one_fragment(struct image *img, struct fragment *frag,
int inaccurate_eof, unsigned ws_rule,
int nth_fragment)
break;
default:
if (apply_verbosely)
- error("invalid start of line: '%c'", first);
+ error(_("invalid start of line: '%c'"), first);
return -1;
}
if (added_blank_line) {
int offset = applied_pos - pos;
if (apply_in_reverse)
offset = 0 - offset;
- fprintf(stderr,
- "Hunk #%d succeeded at %d (offset %d lines).\n",
- nth_fragment, applied_pos + 1, offset);
+ fprintf_ln(stderr,
+ Q_("Hunk #%d succeeded at %d (offset %d line).",
+ "Hunk #%d succeeded at %d (offset %d lines).",
+ offset),
+ nth_fragment, applied_pos + 1, offset);
}
/*
*/
if ((leading != frag->leading) ||
(trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld)"
- " to apply fragment at %d\n",
- leading, trailing, applied_pos+1);
+ fprintf_ln(stderr, _("Context reduced to (%ld/%ld)"
+ " to apply fragment at %d"),
+ leading, trailing, applied_pos+1);
update_image(img, applied_pos, &preimage, &postimage);
} else {
if (apply_verbosely)
- error("while searching for:\n%.*s",
+ error(_("while searching for:\n%.*s"),
(int)(old - oldlines), oldlines);
}
void *dst;
if (!fragment)
- return error("missing binary patch data for '%s'",
+ return error(_("missing binary patch data for '%s'"),
patch->new_name ?
patch->new_name :
patch->old_name);
return -1;
}
+/*
+ * Replace "img" with the result of applying the binary patch.
+ * The binary patch data itself in patch->fragment is still kept
+ * but the preimage prepared by the caller in "img" is freed here
+ * or in the helper function apply_binary_fragment() this calls.
+ */
static int apply_binary(struct image *img, struct patch *patch)
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
* in the patch->fragments->{patch,size}.
*/
if (apply_binary_fragment(img, patch))
- return error("binary patch does not apply to '%s'",
+ return error(_("binary patch does not apply to '%s'"),
name);
/* verify that the result matches */
hash_sha1_file(img->buf, img->len, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
- return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+ return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
while (frag) {
nth++;
if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
- error("patch failed: %s:%ld", name, frag->oldpos);
+ error(_("patch failed: %s:%ld"), name, frag->oldpos);
if (!apply_with_reject)
return -1;
frag->rejected = 1;
if (!(patch->is_copy || patch->is_rename) &&
(tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch)) {
- return error("patch %s has been renamed/deleted",
+ return error(_("patch %s has been renamed/deleted"),
patch->old_name);
}
- /* We have a patched copy in memory use that */
+ /* We have a patched copy in memory; use that. */
strbuf_add(&buf, tpatch->result, tpatch->resultsize);
} else if (cached) {
if (read_file_or_gitlink(ce, &buf))
- return error("read of %s failed", patch->old_name);
+ return error(_("read of %s failed"), patch->old_name);
} else if (patch->old_name) {
if (S_ISGITLINK(patch->old_mode)) {
if (ce) {
/*
* There is no way to apply subproject
* patch without looking at the index.
+ * NEEDSWORK: shouldn't this be flagged
+ * as an error???
*/
+ free_fragment_list(patch->fragments);
patch->fragments = NULL;
}
} else {
if (read_old_data(st, patch->old_name, &buf))
- return error("read of %s failed", patch->old_name);
+ return error(_("read of %s failed"), patch->old_name);
}
}
free(image.line_allocated);
if (0 < patch->is_delete && patch->resultsize)
- return error("removal patch leaves file contents");
+ return error(_("removal patch leaves file contents"));
return 0;
}
if (has_symlink_leading_path(new_name, strlen(new_name)))
return 0;
- return error("%s: already exists in working directory", new_name);
+ return error(_("%s: already exists in working directory"), new_name);
}
else if ((errno != ENOENT) && (errno != ENOTDIR))
return error("%s: %s", new_name, strerror(errno));
if (!(patch->is_copy || patch->is_rename) &&
(tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch))
- return error("%s: has been deleted/renamed", old_name);
+ return error(_("%s: has been deleted/renamed"), old_name);
st_mode = tpatch->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
if (stat_ret && errno != ENOENT)
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
if (to_be_deleted(tpatch))
if (pos < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: does not exist in index", old_name);
+ return error(_("%s: does not exist in index"), old_name);
}
*ce = active_cache[pos];
if (stat_ret < 0) {
return -1;
}
if (!cached && verify_index_match(*ce, st))
- return error("%s: does not match index", old_name);
+ return error(_("%s: does not match index"), old_name);
if (cached)
st_mode = (*ce)->ce_mode;
} else if (stat_ret < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
if (!cached && !tpatch)
if (!patch->old_mode)
patch->old_mode = st_mode;
if ((st_mode ^ patch->old_mode) & S_IFMT)
- return error("%s: wrong type", old_name);
+ return error(_("%s: wrong type"), old_name);
if (st_mode != patch->old_mode)
- warning("%s has type %o, expected %o",
+ warning(_("%s has type %o, expected %o"),
old_name, st_mode, patch->old_mode);
if (!patch->new_mode && !patch->is_delete)
patch->new_mode = st_mode;
is_new:
patch->is_new = 1;
patch->is_delete = 0;
+ free(patch->old_name);
patch->old_name = NULL;
return 0;
}
+/*
+ * Check and apply the patch in-core; leave the result in patch->result
+ * for the caller to write it out to the final destination.
+ */
static int check_patch(struct patch *patch)
{
struct stat st;
if (check_index &&
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
!ok_if_exists)
- return error("%s: already exists in index", new_name);
+ return error(_("%s: already exists in index"), new_name);
if (!cached) {
int err = check_to_create_blob(new_name, ok_if_exists);
if (err)
if (!patch->new_mode)
patch->new_mode = patch->old_mode;
if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
- return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+ return error(_("new mode (%o) of %s does not match old mode (%o)%s%s"),
patch->new_mode, new_name, patch->old_mode,
same ? "" : " of ", same ? "" : old_name);
}
if (apply_data(patch, &st, ce) < 0)
- return error("%s: patch does not apply", name);
+ return error(_("%s: patch does not apply"), name);
patch->rejected = 0;
return 0;
}
while (patch) {
if (apply_verbosely)
say_patch_name(stderr,
- "Checking patch ", patch, "...\n");
+ _("Checking patch %s..."), patch);
err |= check_patch(patch);
patch = patch->next;
}
ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
if (!ce)
- die("make_cache_entry failed for path '%s'", name);
+ die(_("make_cache_entry failed for path '%s'"), name);
if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
die ("Could not add %s to temporary index", name);
}
{
if (update_index) {
if (remove_file_from_cache(patch->old_name) < 0)
- die("unable to remove %s from index", patch->old_name);
+ die(_("unable to remove %s from index"), patch->old_name);
}
if (!cached) {
if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
const char *s = buf;
if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die("corrupt patch for subproject %s", path);
+ die(_("corrupt patch for subproject %s"), path);
} else {
if (!cached) {
if (lstat(path, &st) < 0)
- die_errno("unable to stat newly created file '%s'",
+ die_errno(_("unable to stat newly created file '%s'"),
path);
fill_stat_cache_info(ce, &st);
}
if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
- die("unable to create backing store for newly created file %s", path);
+ die(_("unable to create backing store for newly created file %s"), path);
}
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
- die("unable to add cache entry for %s", path);
+ die(_("unable to add cache entry for %s"), path);
}
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
strbuf_release(&nbuf);
if (close(fd) < 0)
- die_errno("closing file '%s'", path);
+ die_errno(_("closing file '%s'"), path);
return 0;
}
++nr;
}
}
- die_errno("unable to write file '%s' mode %o", path, mode);
+ die_errno(_("unable to write file '%s' mode %o"), path, mode);
}
static void create_file(struct patch *patch)
char namebuf[PATH_MAX];
struct fragment *frag;
int cnt = 0;
+ struct strbuf sb = STRBUF_INIT;
for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
if (!frag->rejected)
if (!cnt) {
if (apply_verbosely)
say_patch_name(stderr,
- "Applied patch ", patch, " cleanly.\n");
+ _("Applied patch %s cleanly."), patch);
return 0;
}
* contents are marked "rejected" at the patch level.
*/
if (!patch->new_name)
- die("internal error");
+ die(_("internal error"));
/* Say this even without --verbose */
- say_patch_name(stderr, "Applying patch ", patch, " with");
- fprintf(stderr, " %d rejects...\n", cnt);
+ strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...",
+ "Applying patch %%s with %d rejects...",
+ cnt),
+ cnt);
+ say_patch_name(stderr, sb.buf, patch);
+ strbuf_release(&sb);
cnt = strlen(patch->new_name);
if (ARRAY_SIZE(namebuf) <= cnt + 5) {
cnt = ARRAY_SIZE(namebuf) - 5;
- warning("truncating .rej filename to %.*s.rej",
+ warning(_("truncating .rej filename to %.*s.rej"),
cnt - 1, patch->new_name);
}
memcpy(namebuf, patch->new_name, cnt);
rej = fopen(namebuf, "w");
if (!rej)
- return error("cannot open %s: %s", namebuf, strerror(errno));
+ return error(_("cannot open %s: %s"), namebuf, strerror(errno));
/* Normal git tools never deal with .rej, so do not pretend
* this is a git patch by saying --git nor give extended
frag;
cnt++, frag = frag->next) {
if (!frag->rejected) {
- fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
continue;
}
- fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
fprintf(rej, "%.*s", frag->size, frag->patch);
if (frag->patch[frag->size-1] != '\n')
fputc('\n', rej);
if (!prefix || p->is_toplevel_relative)
return;
for ( ; p; p = p->next) {
- if (p->new_name == p->old_name) {
- char *prefixed = p->new_name;
- prefix_one(&prefixed);
- p->new_name = p->old_name = prefixed;
- }
- else {
- prefix_one(&p->new_name);
- prefix_one(&p->old_name);
- }
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
}
}
static int apply_patch(int fd, const char *filename, int options)
{
size_t offset;
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT; /* owns the patch text */
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
- /* FIXME - memory leak when using multiple patch files as inputs */
- memset(&fn_table, 0, sizeof(struct string_list));
patch_input_file = filename;
read_patch_file(&buf, fd);
offset = 0;
listp = &patch->next;
}
else {
- /* perhaps free it a bit better? */
- free(patch);
+ free_patch(patch);
skipped_patch++;
}
offset += nr;
}
if (!list && !skipped_patch)
- die("unrecognized input");
+ die(_("unrecognized input"));
if (whitespace_error && (ws_error_action == die_on_ws_error))
apply = 0;
if (check_index) {
if (read_cache() < 0)
- die("unable to read index file");
+ die(_("unable to read index file"));
}
if ((check || apply) &&
if (summary)
summary_patch_list(list);
+ free_patch_list(list);
strbuf_release(&buf);
+ string_list_clear(&fn_table, 0);
return 0;
}
const char *whitespace_option = NULL;
struct option builtin_apply_options[] = {
- { OPTION_CALLBACK, 0, "exclude", NULL, "path",
- "don't apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"),
+ N_("don't apply changes matching the given path"),
0, option_parse_exclude },
- { OPTION_CALLBACK, 0, "include", NULL, "path",
- "apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "include", NULL, N_("path"),
+ N_("apply changes matching the given path"),
0, option_parse_include },
- { OPTION_CALLBACK, 'p', NULL, NULL, "num",
- "remove <num> leading slashes from traditional diff paths",
+ { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
+ N_("remove <num> leading slashes from traditional diff paths"),
0, option_parse_p },
OPT_BOOLEAN(0, "no-add", &no_add,
- "ignore additions made by the patch"),
+ N_("ignore additions made by the patch")),
OPT_BOOLEAN(0, "stat", &diffstat,
- "instead of applying the patch, output diffstat for the input"),
+ N_("instead of applying the patch, output diffstat for the input")),
OPT_NOOP_NOARG(0, "allow-binary-replacement"),
OPT_NOOP_NOARG(0, "binary"),
OPT_BOOLEAN(0, "numstat", &numstat,
- "shows number of added and deleted lines in decimal notation"),
+ N_("shows number of added and deleted lines in decimal notation")),
OPT_BOOLEAN(0, "summary", &summary,
- "instead of applying the patch, output a summary for the input"),
+ N_("instead of applying the patch, output a summary for the input")),
OPT_BOOLEAN(0, "check", &check,
- "instead of applying the patch, see if the patch is applicable"),
+ N_("instead of applying the patch, see if the patch is applicable")),
OPT_BOOLEAN(0, "index", &check_index,
- "make sure the patch is applicable to the current index"),
+ N_("make sure the patch is applicable to the current index")),
OPT_BOOLEAN(0, "cached", &cached,
- "apply a patch without touching the working tree"),
+ N_("apply a patch without touching the working tree")),
OPT_BOOLEAN(0, "apply", &force_apply,
- "also apply the patch (use with --stat/--summary/--check)"),
+ N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
- "build a temporary index based on embedded index information"),
+ N_("build a temporary index based on embedded index information")),
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
+ N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
OPT_INTEGER('C', NULL, &p_context,
- "ensure at least <n> lines of context match"),
- { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
- "detect new or modified lines that have whitespace errors",
+ N_("ensure at least <n> lines of context match")),
+ { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"),
+ N_("detect new or modified lines that have whitespace errors"),
0, option_parse_whitespace },
{ OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
- "apply the patch in reverse"),
+ N_("apply the patch in reverse")),
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
- "don't expect at least one line of context"),
+ N_("don't expect at least one line of context")),
OPT_BOOLEAN(0, "reject", &apply_with_reject,
- "leave the rejected hunks in corresponding *.rej files"),
+ N_("leave the rejected hunks in corresponding *.rej files")),
OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
- "allow overlapping hunks"),
- OPT__VERBOSE(&apply_verbosely, "be verbose"),
+ N_("allow overlapping hunks")),
+ OPT__VERBOSE(&apply_verbosely, N_("be verbose")),
OPT_BIT(0, "inaccurate-eof", &options,
- "tolerate incorrectly detected missing new-line at the end of file",
+ N_("tolerate incorrectly detected missing new-line at the end of file"),
INACCURATE_EOF),
OPT_BIT(0, "recount", &options,
- "do not trust the line counts in the hunk headers",
+ N_("do not trust the line counts in the hunk headers"),
RECOUNT),
- { OPTION_CALLBACK, 0, "directory", NULL, "root",
- "prepend <root> to all filenames",
+ { OPTION_CALLBACK, 0, "directory", NULL, N_("root"),
+ N_("prepend <root> to all filenames"),
0, option_parse_directory },
OPT_END()
};
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
apply = 0;
if (check_index && is_not_gitdir)
- die("--index outside a repository");
+ die(_("--index outside a repository"));
if (cached) {
if (is_not_gitdir)
- die("--cached outside a repository");
+ die(_("--cached outside a repository"));
check_index = 1;
}
for (i = 0; i < argc; i++) {
fd = open(arg, O_RDONLY);
if (fd < 0)
- die_errno("can't open patch '%s'", arg);
+ die_errno(_("can't open patch '%s'"), arg);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
errs |= apply_patch(fd, arg, options);
squelch_whitespace_errors < whitespace_error) {
int squelched =
whitespace_error - squelch_whitespace_errors;
- warning("squelched %d "
- "whitespace error%s",
- squelched,
- squelched == 1 ? "" : "s");
+ warning(Q_("squelched %d whitespace error",
+ "squelched %d whitespace errors",
+ squelched),
+ squelched);
}
if (ws_error_action == die_on_ws_error)
- die("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
+ die(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
if (applied_after_fixing_ws && apply)
warning("%d line%s applied after"
" fixing whitespace errors.",
applied_after_fixing_ws,
applied_after_fixing_ws == 1 ? "" : "s");
else if (whitespace_error)
- warning("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
+ warning(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
}
if (update_index) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ die(_("Unable to write new index file"));
}
return !!errs;
char path[FLEX_ARRAY];
};
+static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
+ xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {NULL};
+
+ xpp.flags = xdl_opts;
+ xecfg.ctxlen = ctxlen;
+ xecfg.hunk_func = hunk_func;
+ ecb.priv = cb_data;
+ return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
+}
+
/*
* Prepare diff_filespec and convert it using diff textconv API
* if the textconv driver exists.
long tlno;
};
-static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+static int blame_chunk_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct blame_chunk_cb_data *d = data;
- blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
- d->plno = p_next;
- d->tlno = t_next;
+ blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
/*
int last_in_target;
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
- xpparam_t xpp;
- xdemitconf_t xecfg;
+
memset(&d, 0, sizeof(d));
d.sb = sb; d.target = target; d.parent = parent;
last_in_target = find_last_in_target(sb, target);
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
num_get_patch++;
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 0;
- xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
+ diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
/* The rest (i.e. anything after tlno) are the same as the parent */
blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
long tlno;
};
-static void handle_split_cb(void *data, long same, long p_next, long t_next)
+static int handle_split_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct handle_split_cb_data *d = data;
- handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
- d->plno = p_next;
- d->tlno = t_next;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
+ d->split);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
/*
int cnt;
mmfile_t file_o;
struct handle_split_cb_data d;
- xpparam_t xpp;
- xdemitconf_t xecfg;
+
memset(&d, 0, sizeof(d));
d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
/*
* file_o is a part of final image we are annotating.
* file_p partially may match that image.
*/
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 1;
memset(split, 0, sizeof(struct blame_entry [3]));
- xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
+ diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
/* remainder, if any, all match the preimage */
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
#include "branch.h"
#include "diff.h"
#include "revision.h"
+#include "string-list.h"
+#include "column.h"
static const char * const builtin_branch_usage[] = {
"git branch [options] [-r | -a] [--merged | --no-merged]",
} merge_filter;
static unsigned char merge_filter_ref[20];
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
static int parse_branch_color_slot(const char *var, int ofs)
{
if (!strcasecmp(var+ofs, "plain"))
static int git_branch_config(const char *var, const char *value, void *cb)
{
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
return merged;
}
-static int delete_branches(int argc, const char **argv, int force, int kinds)
+static int delete_branches(int argc, const char **argv, int force, int kinds,
+ int quiet)
{
struct commit *rev, *head_rev = NULL;
unsigned char sha1[20];
char *name = NULL;
- const char *fmt, *remote;
+ const char *fmt;
int i;
int ret = 0;
+ int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
switch (kinds) {
case REF_REMOTE_BRANCH:
fmt = "refs/remotes/%s";
- /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
- remote = _("remote ");
+ /* For subsequent UI messages */
+ remote_branch = 1;
+
force = 1;
break;
case REF_LOCAL_BRANCH:
fmt = "refs/heads/%s";
- remote = "";
break;
default:
die(_("cannot use -a with -d"));
name = xstrdup(mkpath(fmt, bname.buf));
if (read_ref(name, sha1)) {
- error(_("%sbranch '%s' not found."),
- remote, bname.buf);
+ error(remote_branch
+ ? _("remote branch '%s' not found.")
+ : _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
}
}
if (delete_ref(name, sha1, 0)) {
- error(_("Error deleting %sbranch '%s'"), remote,
+ error(remote_branch
+ ? _("Error deleting remote branch '%s'")
+ : _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
} else {
struct strbuf buf = STRBUF_INIT;
- printf(_("Deleted %sbranch %s (was %s).\n"), remote,
- bname.buf,
- find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ if (!quiet)
+ printf(remote_branch
+ ? _("Deleted remote branch %s (was %s).\n")
+ : _("Deleted branch %s (was %s).\n"),
+ bname.buf,
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
strbuf_addf(&buf, "branch.%s", bname.buf);
if (git_config_rename_section(buf.buf, NULL) < 0)
warning(_("Update of config-file failed"));
int show_upstream_ref)
{
int ours, theirs;
+ char *ref = NULL;
struct branch *branch = branch_get(branch_name);
if (!stat_tracking_info(branch, &ours, &theirs)) {
return;
}
- strbuf_addch(stat, '[');
if (show_upstream_ref)
- strbuf_addf(stat, "%s: ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
- if (!ours)
- strbuf_addf(stat, _("behind %d] "), theirs);
- else if (!theirs)
- strbuf_addf(stat, _("ahead %d] "), ours);
- else
- strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs);
+ ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+ if (!ours) {
+ if (ref)
+ strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs);
+ else
+ strbuf_addf(stat, _("[behind %d]"), theirs);
+
+ } else if (!theirs) {
+ if (ref)
+ strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours);
+ else
+ strbuf_addf(stat, _("[ahead %d]"), ours);
+ } else {
+ if (ref)
+ strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
+ ref, ours, theirs);
+ else
+ strbuf_addf(stat, _("[ahead %d, behind %d]"),
+ ours, theirs);
+ }
+ strbuf_addch(stat, ' ');
+ free(ref);
}
static int matches_merge_filter(struct commit *commit)
else if (verbose)
/* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
add_verbose_info(&out, item, verbose, abbrev);
- printf("%s\n", out.buf);
+ if (column_active(colopts)) {
+ assert(!verbose && "--column and --verbose are incompatible");
+ string_list_append(&output, out.buf);
+ } else {
+ printf("%s\n", out.buf);
+ }
strbuf_release(&name);
strbuf_release(&out);
}
fp = fopen(git_path(edit_description), "w");
if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
strbuf_release(&buf);
- return error(_("could not write branch description template: %s\n"),
+ return error(_("could not write branch description template: %s"),
strerror(errno));
}
strbuf_reset(&buf);
int delete = 0, rename = 0, force_create = 0, list = 0;
int verbose = 0, abbrev = -1, detached = 0;
int reflog = 0, edit_description = 0;
+ int quiet = 0;
enum branch_track track;
int kinds = REF_LOCAL_BRANCH;
struct commit_list *with_commit = NULL;
OPT_GROUP("Generic options"),
OPT__VERBOSE(&verbose,
"show hash and subject, give twice for upstream branch"),
+ OPT__QUIET(&quiet, "suppress informational messages"),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
+ OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
OPT_END(),
};
}
hashcpy(merge_filter_ref, head_sha1);
+
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
if (abbrev == -1)
abbrev = DEFAULT_ABBREV;
+ finalize_colopts(&colopts, -1);
+ if (verbose) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and --verbose are incompatible"));
+ colopts = 0;
+ }
if (delete)
- return delete_branches(argc, argv, delete > 1, kinds);
- else if (list)
- return print_ref_list(kinds, detached, verbose, abbrev,
- with_commit, argv);
+ return delete_branches(argc, argv, delete > 1, kinds, quiet);
+ else if (list) {
+ int ret = print_ref_list(kinds, detached, verbose, abbrev,
+ with_commit, argv);
+ print_columns(&output, colopts, NULL);
+ string_list_clear(&output, 0);
+ return ret;
+ }
else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
if (kinds != REF_LOCAL_BRANCH)
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
- force_create, reflog, 0, track);
+ force_create, reflog, 0, quiet, track);
} else
usage_with_options(builtin_branch_usage, options);
#include "parse-options.h"
#include "diff.h"
#include "userdiff.h"
+#include "streaming.h"
#define BATCH 1
#define BATCH_CHECK 2
return cmd_ls_tree(2, ls_args, NULL);
}
+ if (type == OBJ_BLOB)
+ return stream_blob_to_fd(1, sha1, NULL, 0);
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
break;
case 0:
+ if (type_from_string(exp_type) == OBJ_BLOB) {
+ unsigned char blob_sha1[20];
+ if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
+ enum object_type type;
+ unsigned long size;
+ char *buffer = read_sha1_file(sha1, &type, &size);
+ if (memcmp(buffer, "object ", 7) ||
+ get_sha1_hex(buffer + 7, blob_sha1))
+ die("%s not a valid tag", sha1_to_hex(sha1));
+ free(buffer);
+ } else
+ hashcpy(blob_sha1, sha1);
+
+ if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+ return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+ /*
+ * we attempted to dereference a tag to a blob
+ * and failed; there may be new dereference
+ * mechanisms this code is not aware of.
+ * fall-back to the usual case.
+ */
+ }
buf = read_object_with_reference(sha1, exp_type, &size, NULL);
break;
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->new_branch_force ? 1 : 0,
+ opts->quiet,
opts->track);
new->name = opts->new_branch;
setup_branch_path(new);
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+ "git column [options]",
+ NULL
+};
+static unsigned int colopts;
+
+static int column_config(const char *var, const char *value, void *cb)
+{
+ return git_column_config(var, value, cb, &colopts);
+}
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct strbuf sb = STRBUF_INIT;
+ struct column_options copts;
+ const char *command = NULL, *real_command = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
+ OPT_COLUMN(0, "mode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
+ OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
+ OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
+ OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
+ OPT_END()
+ };
+
+ /* This one is special and must be the first one */
+ if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+ command = argv[1] + 10;
+ git_config(column_config, (void *)command);
+ } else
+ git_config(column_config, NULL);
+
+ memset(&copts, 0, sizeof(copts));
+ copts.width = term_columns();
+ copts.padding = 1;
+ argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+ if (argc)
+ usage_with_options(builtin_column_usage, options);
+ if (real_command || command) {
+ if (!real_command || !command || strcmp(real_command, command))
+ die(_("--command must be the first argument"));
+ }
+ finalize_colopts(&colopts, -1);
+ while (!strbuf_getline(&sb, stdin, '\n'))
+ string_list_append(&list, sb.buf);
+
+ print_columns(&list, colopts, &copts);
+ return 0;
+}
#include "quote.h"
#include "submodule.h"
#include "gpg-interface.h"
+#include "column.h"
static const char * const builtin_commit_usage[] = {
"git commit [options] [--] <filepattern>...",
s->whence = whence;
}
-static const char *whence_s(void)
-{
- const char *s = "";
-
- switch (whence) {
- case FROM_COMMIT:
- break;
- case FROM_MERGE:
- s = _("merge");
- break;
- case FROM_CHERRY_PICK:
- s = _("cherry-pick");
- break;
- }
-
- return s;
-}
-
static void rollback_index_files(void)
{
switch (commit_style) {
*/
commit_style = COMMIT_PARTIAL;
- if (whence != FROM_COMMIT)
- die(_("cannot do a partial commit during a %s."), whence_s());
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("cannot do a partial commit during a merge."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("cannot do a partial commit during a cherry-pick."));
+ }
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
char *ai_tmp, *ci_tmp;
if (whence != FROM_COMMIT)
status_printf_ln(s, GIT_COLOR_NORMAL,
- _("\n"
- "It looks like you may be committing a %s.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n"
- ""),
- whence_s(),
+ whence == FROM_MERGE
+ ? _("\n"
+ "It looks like you may be committing a merge.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n")
+ : _("\n"
+ "It looks like you may be committing a cherry-pick.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n"),
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
fprintf(s->fp, "\n");
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."));
if (cleanup_mode == CLEANUP_ALL)
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
- "with '#' will be ignored, and an empty"
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '#' will be ignored, and an empty"
" message aborts the commit.\n"));
else /* CLEANUP_SPACE, that is. */
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
"with '#' will be kept; you may remove them"
" yourself if you want to.\n"
"An empty message aborts the commit.\n"));
/* Sanity check options */
if (amend && !current_head)
die(_("You have nothing to amend."));
- if (amend && whence != FROM_COMMIT)
- die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+ if (amend && whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot amend."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("You are in the middle of a cherry-pick -- cannot amend."));
+ }
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
if (use_message)
{
struct wt_status *s = cb;
+ if (!prefixcmp(k, "column."))
+ return git_column_config(k, v, "status", &s->colopts);
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
{ OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
"ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_COLUMN(0, "column", &s.colopts, "list untracked files in columns"),
OPT_END(),
};
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
+ finalize_colopts(&s.colopts, -1);
if (s.null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
wt_status_prepare(&s);
git_config(git_commit_config, &s);
determine_whence(&s);
+ s.colopts = 0;
if (get_sha1("HEAD", sha1))
current_head = NULL;
int result = 0;
struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
const char *dangling_msg = dry_run
- ? _(" (%s will become dangling)\n")
- : _(" (%s has become dangling)\n");
+ ? _(" (%s will become dangling)")
+ : _(" (%s has become dangling)");
for (ref = stale_refs; ref; ref = ref->next) {
if (!dry_run)
merge_log_config = DEFAULT_MERGE_LOG_LEN;
} else if (!strcmp(key, "merge.branchdesc")) {
use_branch_desc = git_config_bool(key, value);
+ } else {
+ return git_default_config(key, value, cb);
}
return 0;
}
strbuf_release(&desc);
}
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ char name_buf[MAX_GITNAME], *name, *name_end;
+ struct string_list_item *elem;
+ const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+
+ name = strstr(commit->buffer, field);
+ if (!name)
+ return;
+ name += strlen(field);
+ name_end = strchrnul(name, '<');
+ if (*name_end)
+ name_end--;
+ while (isspace(*name_end) && name <= name_end)
+ name_end--;
+ if (name_end < name || name + MAX_GITNAME <= name_end)
+ return;
+ memcpy(name_buf, name, name_end - name + 1);
+ name_buf[name_end - name + 1] = '\0';
+
+ elem = string_list_lookup(people, name_buf);
+ if (!elem) {
+ elem = string_list_insert(people, name_buf);
+ elem->util = (void *)0;
+ }
+ elem->util = (void*)(util_as_integral(elem) + 1);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_, *b = b_;
+ return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+ if (people->nr == 1)
+ strbuf_addf(out, "%s", people->items[0].string);
+ else if (people->nr == 2)
+ strbuf_addf(out, "%s (%d) and %s (%d)",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]),
+ people->items[1].string,
+ (int)util_as_integral(&people->items[1]));
+ else if (people->nr)
+ strbuf_addf(out, "%s (%d) and others",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+ struct string_list *them,
+ int kind)
+{
+ const char *label;
+ const char *me;
+
+ if (kind == 'a') {
+ label = "\nBy ";
+ me = git_author_info(IDENT_NO_DATE);
+ } else {
+ label = "\nvia ";
+ me = git_committer_info(IDENT_NO_DATE);
+ }
+
+ if (!them->nr ||
+ (them->nr == 1 &&
+ me &&
+ (me = skip_prefix(me, them->items->string)) != NULL &&
+ skip_prefix(me, " <")))
+ return;
+ strbuf_addstr(out, label);
+ add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+ struct string_list *authors,
+ struct string_list *committers)
+{
+ if (authors->nr)
+ qsort(authors->items,
+ authors->nr, sizeof(authors->items[0]),
+ cmp_string_list_util_as_integral);
+ if (committers->nr)
+ qsort(committers->items,
+ committers->nr, sizeof(committers->items[0]),
+ cmp_string_list_util_as_integral);
+
+ credit_people(out, authors, 'a');
+ credit_people(out, committers, 'c');
+}
+
static void shortlog(const char *name,
struct origin_data *origin_data,
struct commit *head,
struct commit *commit;
struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP;
+ struct string_list authors = STRING_LIST_INIT_DUP;
+ struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
const unsigned char *sha1 = origin_data->sha1;
return;
setup_revisions(0, NULL, rev, NULL);
- rev->ignore_merges = 1;
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0};
- /* ignore merges */
- if (commit->parents && commit->parents->next)
+ if (commit->parents && commit->parents->next) {
+ /* do not list a merge but count committer */
+ record_person('c', &committers, commit);
continue;
-
+ }
+ if (!count)
+ /* the 'tip' committer */
+ record_person('c', &committers, commit);
+ record_person('a', &authors, commit);
count++;
if (subjects.nr > limit)
continue;
string_list_append(&subjects, strbuf_detach(&sb, NULL));
}
+ add_people_info(out, &authors, &committers);
if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
rev->commits = NULL;
rev->pending.nr = 0;
+ string_list_clear(&authors, 0);
+ string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}
#include "parse-options.h"
#include "dir.h"
#include "progress.h"
+#include "streaming.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
if (!(f = fopen(filename, "w")))
die_errno("Could not open '%s'", filename);
if (obj->type == OBJ_BLOB) {
- enum object_type type;
- unsigned long size;
- char *buf = read_sha1_file(obj->sha1,
- &type, &size);
- if (buf && fwrite(buf, 1, size, f) != size)
+ if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
die_errno("Could not write '%s'", filename);
- free(buf);
} else
fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
if (fclose(f))
#include "common-cmds.h"
#include "parse-options.h"
#include "run-command.h"
+#include "column.h"
#include "help.h"
static struct man_viewer_list {
};
static int show_all = 0;
+static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static struct option builtin_help_options[] = {
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
return HELP_FORMAT_INFO;
if (!strcmp(format, "web") || !strcmp(format, "html"))
return HELP_FORMAT_WEB;
- die("unrecognized help format '%s'", format);
+ die(_("unrecognized help format '%s'"), format);
}
static const char *get_man_viewer_info(const char *name)
ec_process.err = -1;
ec_process.stdout_to_stderr = 1;
if (start_command(&ec_process))
- return error("Failed to start emacsclient.");
+ return error(_("Failed to start emacsclient."));
strbuf_read(&buffer, ec_process.err, 20);
close(ec_process.err);
if (prefixcmp(buffer.buf, "emacsclient")) {
strbuf_release(&buffer);
- return error("Failed to parse emacsclient version.");
+ return error(_("Failed to parse emacsclient version."));
}
strbuf_remove(&buffer, 0, strlen("emacsclient"));
if (version < 22) {
strbuf_release(&buffer);
- return error("emacsclient version '%d' too old (< 22).",
+ return error(_("emacsclient version '%d' too old (< 22)."),
version);
}
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
if (!path)
path = "man";
execlp(path, "man", page, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
static void exec_man_cmd(const char *cmd, const char *page)
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
- warning("failed to exec '%s': %s", cmd, strerror(errno));
+ warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
}
static void add_man_viewer(const char *name)
if (supported_man_viewer(name, len))
do_add_man_viewer_info(name, len, value);
else
- warning("'%s': path for unsupported man viewer.\n"
- "Please consider using 'man.<tool>.cmd' instead.",
+ warning(_("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead."),
name);
return 0;
const char *value)
{
if (supported_man_viewer(name, len))
- warning("'%s': cmd for supported man viewer.\n"
- "Please consider using 'man.<tool>.path' instead.",
+ warning(_("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead."),
name);
else
do_add_man_viewer_info(name, len, value);
static int git_help_config(const char *var, const char *value, void *cb)
{
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
longest = strlen(common_cmds[i].name);
}
- puts("The most commonly used git commands are:");
+ puts(_("The most commonly used git commands are:"));
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
printf(" %s ", common_cmds[i].name);
mput_char(' ', longest - strlen(common_cmds[i].name));
- puts(common_cmds[i].help);
+ puts(_(common_cmds[i].help));
}
}
else if (info)
exec_man_cmd(info, page);
else
- warning("'%s': unknown man viewer.", name);
+ warning(_("'%s': unknown man viewer."), name);
}
static void show_man_page(const char *git_cmd)
if (fallback)
exec_viewer(fallback, page);
exec_viewer("man", page);
- die("no man viewer handled the request");
+ die(_("no man viewer handled the request"));
}
static void show_info_page(const char *git_cmd)
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
- die("no info viewer handled the request");
+ die(_("no info viewer handled the request"));
}
static void get_html_page_path(struct strbuf *page_path, const char *page)
/* Check that we have a git documentation directory. */
if (stat(mkpath("%s/git.html", html_path), &st)
|| !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", html_path);
+ die(_("'%s': not a documentation directory."), html_path);
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
parsed_help_format = help_format;
if (show_all) {
- printf("usage: %s\n\n", git_usage_string);
- list_commands("git commands", &main_cmds, &other_cmds);
- printf("%s\n", git_more_info_string);
+ git_config(git_help_config, NULL);
+ printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
+ list_commands(colopts, &main_cmds, &other_cmds);
+ printf("%s\n", _(git_more_info_string));
return 0;
}
if (!argv[0]) {
- printf("usage: %s\n\n", git_usage_string);
+ printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
list_common_cmds_help();
- printf("\n%s\n", git_more_info_string);
+ printf("\n%s\n", _(git_more_info_string));
return 0;
}
alias = alias_lookup(argv[0]);
if (alias && !is_git_command(argv[0])) {
- printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
return 0;
}
#include "progress.h"
#include "fsck.h"
#include "exec_cmd.h"
+#include "thread-utils.h"
static const char index_pack_usage[] =
"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
int ofs_first, ofs_last;
};
+#if !defined(NO_PTHREADS) && defined(NO_PREAD)
+/* NO_PREAD uses compat/pread.c, which is not thread-safe. Disable threading. */
+#define NO_PTHREADS
+#endif
+
+struct thread_local {
+#ifndef NO_PTHREADS
+ pthread_t thread;
+#endif
+ struct base_data *base_cache;
+ size_t base_cache_used;
+};
+
/*
* Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
* to memcmp() only the first 20 bytes.
static struct object_entry *objects;
static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
+static struct thread_local nothread_data;
static int nr_objects;
static int nr_deltas;
static int nr_resolved_deltas;
+static int nr_threads;
static int from_stdin;
static int strict;
static uint32_t input_crc32;
static int input_fd, output_fd, pack_fd;
+#ifndef NO_PTHREADS
+
+static struct thread_local *thread_data;
+static int nr_dispatched;
+static int threads_active;
+
+static pthread_mutex_t read_mutex;
+#define read_lock() lock_mutex(&read_mutex)
+#define read_unlock() unlock_mutex(&read_mutex)
+
+static pthread_mutex_t counter_mutex;
+#define counter_lock() lock_mutex(&counter_mutex)
+#define counter_unlock() unlock_mutex(&counter_mutex)
+
+static pthread_mutex_t work_mutex;
+#define work_lock() lock_mutex(&work_mutex)
+#define work_unlock() unlock_mutex(&work_mutex)
+
+static pthread_key_t key;
+
+static inline void lock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_lock(mutex);
+}
+
+static inline void unlock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_unlock(mutex);
+}
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_thread(void)
+{
+ init_recursive_mutex(&read_mutex);
+ pthread_mutex_init(&counter_mutex, NULL);
+ pthread_mutex_init(&work_mutex, NULL);
+ pthread_key_create(&key, NULL);
+ thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ threads_active = 1;
+}
+
+static void cleanup_thread(void)
+{
+ if (!threads_active)
+ return;
+ threads_active = 0;
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&counter_mutex);
+ pthread_mutex_destroy(&work_mutex);
+ pthread_key_delete(key);
+ free(thread_data);
+}
+
+#else
+
+#define read_lock()
+#define read_unlock()
+
+#define counter_lock()
+#define counter_unlock()
+
+#define work_lock()
+#define work_unlock()
+
+#endif
+
+
static int mark_link(struct object *obj, int type, void *data)
{
if (!obj)
return -1;
if (type != OBJ_ANY && obj->type != type)
- die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+ die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1));
obj->flags |= FLAG_LINK;
return 0;
unsigned long size;
int type = sha1_object_info(obj->sha1, &size);
if (type != obj->type || type <= 0)
- die("object of unexpected type");
+ die(_("object of unexpected type"));
obj->flags |= FLAG_CHECKED;
return;
}
if (min <= input_len)
return input_buffer + input_offset;
if (min > sizeof(input_buffer))
- die("cannot fill %d bytes", min);
+ die(Q_("cannot fill %d byte",
+ "cannot fill %d bytes",
+ min),
+ min);
flush();
do {
ssize_t ret = xread(input_fd, input_buffer + input_len,
sizeof(input_buffer) - input_len);
if (ret <= 0) {
if (!ret)
- die("early EOF");
- die_errno("read error on input");
+ die(_("early EOF"));
+ die_errno(_("read error on input"));
}
input_len += ret;
if (from_stdin)
static void use(int bytes)
{
if (bytes > input_len)
- die("used more bytes than were available");
+ die(_("used more bytes than were available"));
input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
input_len -= bytes;
input_offset += bytes;
/* make sure off_t is sufficiently large not to wrap */
if (signed_add_overflows(consumed_bytes, bytes))
- die("pack too large for current definition of off_t");
+ die(_("pack too large for current definition of off_t"));
consumed_bytes += bytes;
}
} else
output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd < 0)
- die_errno("unable to create '%s'", pack_name);
+ die_errno(_("unable to create '%s'"), pack_name);
pack_fd = output_fd;
} else {
input_fd = open(pack_name, O_RDONLY);
if (input_fd < 0)
- die_errno("cannot open packfile '%s'", pack_name);
+ die_errno(_("cannot open packfile '%s'"), pack_name);
output_fd = -1;
pack_fd = input_fd;
}
/* Header consistency check */
if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("pack signature mismatch");
+ die(_("pack signature mismatch"));
if (!pack_version_ok(hdr->hdr_version))
die("pack version %"PRIu32" unsupported",
ntohl(hdr->hdr_version));
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die("pack has bad object at offset %lu: %s", offset, buf);
+ die(_("pack has bad object at offset %lu: %s"), offset, buf);
+}
+
+static inline struct thread_local *get_thread_data(void)
+{
+#ifndef NO_PTHREADS
+ if (threads_active)
+ return pthread_getspecific(key);
+ assert(!threads_active &&
+ "This should only be reached when all threads are gone");
+#endif
+ return ¬hread_data;
+}
+
+#ifndef NO_PTHREADS
+static void set_thread_data(struct thread_local *data)
+{
+ if (threads_active)
+ pthread_setspecific(key, data);
}
+#endif
static struct base_data *alloc_base_data(void)
{
if (c->data) {
free(c->data);
c->data = NULL;
- base_cache_used -= c->size;
+ get_thread_data()->base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
struct base_data *b;
- for (b = base_cache;
- base_cache_used > delta_base_cache_limit && b;
+ struct thread_local *data = get_thread_data();
+ for (b = data->base_cache;
+ data->base_cache_used > delta_base_cache_limit && b;
b = b->child) {
if (b->data && b != retain)
free_base_data(b);
if (base)
base->child = c;
else
- base_cache = c;
+ get_thread_data()->base_cache = c;
c->base = base;
c->child = NULL;
if (c->data)
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
if (base)
base->child = NULL;
else
- base_cache = NULL;
+ get_thread_data()->base_cache = NULL;
free_base_data(c);
}
use(input_len - stream.avail_in);
} while (status == Z_OK);
if (stream.total_out != size || status != Z_STREAM_END)
- bad_object(offset, "inflate returned %d", status);
+ bad_object(offset, _("inflate returned %d"), status);
git_inflate_end(&stream);
return buf;
}
while (c & 128) {
base_offset += 1;
if (!base_offset || MSB(base_offset, 7))
- bad_object(obj->idx.offset, "offset value overflow for delta base object");
+ bad_object(obj->idx.offset, _("offset value overflow for delta base object"));
p = fill(1);
c = *p;
use(1);
}
delta_base->offset = obj->idx.offset - base_offset;
if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
- bad_object(obj->idx.offset, "delta base offset is out of bound");
+ bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_TAG:
break;
default:
- bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+ bad_object(obj->idx.offset, _("unknown object type %d"), obj->type);
}
obj->hdr_size = consumed_bytes - obj->idx.offset;
ssize_t n = (len < 64*1024) ? len : 64*1024;
n = pread(pack_fd, inbuf, n, from);
if (n < 0)
- die_errno("cannot pread pack file");
+ die_errno(_("cannot pread pack file"));
if (!n)
- die("premature end of pack file, %lu bytes missing", len);
+ die(Q_("premature end of pack file, %lu byte missing",
+ "premature end of pack file, %lu bytes missing",
+ len),
+ len);
from += n;
len -= n;
stream.next_in = inbuf;
/* This has been inflated OK when first encountered, so... */
if (status != Z_STREAM_END || stream.total_out != obj->size)
- die("serious inflate inconsistency");
+ die(_("serious inflate inconsistency"));
git_inflate_end(&stream);
free(inbuf);
enum object_type type, unsigned char *sha1)
{
hash_sha1_file(data, size, typename(type), sha1);
+ read_lock();
if (has_sha1_file(sha1)) {
void *has_data;
enum object_type has_type;
unsigned long has_size;
has_data = read_sha1_file(sha1, &has_type, &has_size);
+ read_unlock();
if (!has_data)
- die("cannot read existing object %s", sha1_to_hex(sha1));
+ die(_("cannot read existing object %s"), sha1_to_hex(sha1));
if (size != has_size || type != has_type ||
memcmp(data, has_data, size) != 0)
- die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+ die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
free(has_data);
- }
+ } else
+ read_unlock();
+
if (strict) {
+ read_lock();
if (type == OBJ_BLOB) {
struct blob *blob = lookup_blob(sha1);
if (blob)
blob->object.flags |= FLAG_CHECKED;
else
- die("invalid blob object %s", sha1_to_hex(sha1));
+ die(_("invalid blob object %s"), sha1_to_hex(sha1));
} else {
struct object *obj;
int eaten;
*/
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
- die("invalid %s", typename(type));
+ die(_("invalid %s"), typename(type));
if (fsck_object(obj, 1, fsck_error_function))
- die("Error in object");
+ die(_("Error in object"));
if (fsck_walk(obj, mark_link, NULL))
- die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+ die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
}
obj->flags |= FLAG_CHECKED;
}
+ read_unlock();
}
}
if (!delta_nr) {
c->data = get_data_from_pack(obj);
c->size = obj->size;
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
for (; delta_nr > 0; delta_nr--) {
&c->size);
free(raw);
if (!c->data)
- bad_object(obj->idx.offset, "failed to apply delta");
- base_cache_used += c->size;
+ bad_object(obj->idx.offset, _("failed to apply delta"));
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
free(delta);
delta_data, delta_obj->size, &result->size);
free(delta_data);
if (!result->data)
- bad_object(delta_obj->idx.offset, "failed to apply delta");
+ bad_object(delta_obj->idx.offset, _("failed to apply delta"));
sha1_object(result->data, result->size, delta_obj->real_type,
delta_obj->idx.sha1);
+ counter_lock();
nr_resolved_deltas++;
+ counter_unlock();
}
static struct base_data *find_unresolved_deltas_1(struct base_data *base,
objects[delta_b->obj_no].type);
}
-/* Parse all objects and return the pack content SHA1 hash */
+static void resolve_base(struct object_entry *obj)
+{
+ struct base_data *base_obj = alloc_base_data();
+ base_obj->obj = obj;
+ base_obj->data = NULL;
+ find_unresolved_deltas(base_obj);
+}
+
+#ifndef NO_PTHREADS
+static void *threaded_second_pass(void *data)
+{
+ set_thread_data(data);
+ for (;;) {
+ int i;
+ work_lock();
+ display_progress(progress, nr_resolved_deltas);
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ i = nr_dispatched++;
+ work_unlock();
+
+ resolve_base(&objects[i]);
+ }
+ return NULL;
+}
+#endif
+
+/*
+ * First pass:
+ * - find locations of all objects;
+ * - calculate SHA1 of all non-delta objects;
+ * - remember base (SHA1 or offset) for all deltas.
+ */
static void parse_pack_objects(unsigned char *sha1)
{
int i;
struct delta_entry *delta = deltas;
struct stat st;
- /*
- * First pass:
- * - find locations of all objects;
- * - calculate SHA1 of all non-delta objects;
- * - remember base (SHA1 or offset) for all deltas.
- */
if (verbose)
progress = start_progress(
- from_stdin ? "Receiving objects" : "Indexing objects",
+ from_stdin ? _("Receiving objects") : _("Indexing objects"),
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
flush();
git_SHA1_Final(sha1, &input_ctx);
if (hashcmp(fill(20), sha1))
- die("pack is corrupted (SHA1 mismatch)");
+ die(_("pack is corrupted (SHA1 mismatch)"));
use(20);
/* If input_fd is a file, we should have reached its end now. */
if (fstat(input_fd, &st))
- die_errno("cannot fstat packfile");
+ die_errno(_("cannot fstat packfile"));
if (S_ISREG(st.st_mode) &&
lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
- die("pack has junk at the end");
+ die(_("pack has junk at the end"));
+}
+
+/*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ */
+static void resolve_deltas(void)
+{
+ int i;
if (!nr_deltas)
return;
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
- /*
- * Second pass:
- * - for all non-delta objects, look if it is used as a base for
- * deltas;
- * - if used as a base, uncompress the object and apply all deltas,
- * recursively checking if the resulting object is used as a base
- * for some more deltas.
- */
if (verbose)
- progress = start_progress("Resolving deltas", nr_deltas);
+ progress = start_progress(_("Resolving deltas"), nr_deltas);
+
+#ifndef NO_PTHREADS
+ nr_dispatched = 0;
+ if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
+ init_thread();
+ for (i = 0; i < nr_threads; i++) {
+ int ret = pthread_create(&thread_data[i].thread, NULL,
+ threaded_second_pass, thread_data + i);
+ if (ret)
+ die("unable to create thread: %s", strerror(ret));
+ }
+ for (i = 0; i < nr_threads; i++)
+ pthread_join(thread_data[i].thread, NULL);
+ cleanup_thread();
+ return;
+ }
+#endif
+
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- struct base_data *base_obj = alloc_base_data();
if (is_delta_type(obj->type))
continue;
- base_obj->obj = obj;
- base_obj->data = NULL;
- find_unresolved_deltas(base_obj);
+ resolve_base(obj);
display_progress(progress, nr_resolved_deltas);
}
}
+/*
+ * Third pass:
+ * - append objects to convert thin pack to full pack if required
+ * - write the final 20-byte SHA-1
+ */
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
+static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
+{
+ if (nr_deltas == nr_resolved_deltas) {
+ stop_progress(&progress);
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ return;
+ }
+
+ if (fix_thin_pack) {
+ struct sha1file *f;
+ unsigned char read_sha1[20], tail_sha1[20];
+ char msg[48];
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die(_("confusion beyond insanity"));
+ objects = xrealloc(objects,
+ (nr_objects + nr_unresolved + 1)
+ * sizeof(*objects));
+ f = sha1fd(output_fd, curr_pack);
+ fix_unresolved_deltas(f, nr_unresolved);
+ sprintf(msg, "completed with %d local objects",
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg);
+ sha1close(f, tail_sha1, 0);
+ hashcpy(read_sha1, pack_sha1);
+ fixup_pack_header_footer(output_fd, pack_sha1,
+ curr_pack, nr_objects,
+ read_sha1, consumed_bytes-20);
+ if (hashcmp(read_sha1, tail_sha1) != 0)
+ die("Unexpected tail checksum for %s "
+ "(disk corruption?)", curr_pack);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die(Q_("pack has %d unresolved delta",
+ "pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas),
+ nr_deltas - nr_resolved_deltas);
+}
+
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
{
git_zstream stream;
} while (status == Z_OK);
if (status != Z_STREAM_END)
- die("unable to deflate appended object (%d)", status);
+ die(_("unable to deflate appended object (%d)"), status);
size = stream.total_out;
git_deflate_end(&stream);
return size;
if (check_sha1_signature(d->base.sha1, base_obj->data,
base_obj->size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+ die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
base_obj->obj = append_obj_to_pack(f, d->base.sha1,
base_obj->data, base_obj->size, type);
find_unresolved_deltas(base_obj);
fsync_or_die(output_fd, curr_pack_name);
err = close(output_fd);
if (err)
- die_errno("error while closing pack file");
+ die_errno(_("error while closing pack file"));
}
if (keep_msg) {
if (keep_fd < 0) {
if (errno != EEXIST)
- die_errno("cannot write keep file '%s'",
+ die_errno(_("cannot write keep file '%s'"),
keep_name);
} else {
if (keep_msg_len > 0) {
write_or_die(keep_fd, "\n", 1);
}
if (close(keep_fd) != 0)
- die_errno("cannot close written keep file '%s'",
+ die_errno(_("cannot close written keep file '%s'"),
keep_name);
report = "keep";
}
final_pack_name = name;
}
if (move_temp_to_file(curr_pack_name, final_pack_name))
- die("cannot store pack file");
+ die(_("cannot store pack file"));
} else if (from_stdin)
chmod(final_pack_name, 0444);
final_index_name = name;
}
if (move_temp_to_file(curr_index_name, final_index_name))
- die("cannot store index file");
+ die(_("cannot store index file"));
} else
chmod(final_index_name, 0444);
die("bad pack.indexversion=%"PRIu32, opts->version);
return 0;
}
+ if (!strcmp(k, "pack.threads")) {
+ nr_threads = git_config_int(k, v);
+ if (nr_threads < 0)
+ die("invalid number of threads specified (%d)",
+ nr_threads);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, ignoring %s", k);
+ nr_threads = 1;
+#endif
+ return 0;
+ }
return git_default_config(k, v, cb);
}
struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
if (!p)
- die("Cannot open existing pack file '%s'", pack_name);
+ die(_("Cannot open existing pack file '%s'"), pack_name);
if (open_pack_index(p))
- die("Cannot open existing pack idx file for '%s'", pack_name);
+ die(_("Cannot open existing pack idx file for '%s'"), pack_name);
/* Read the attributes from the existing idx file */
opts->version = p->index_version;
}
if (baseobjects)
- printf("non delta: %d object%s\n",
- baseobjects, baseobjects > 1 ? "s" : "");
+ printf_ln(Q_("non delta: %d object",
+ "non delta: %d objects",
+ baseobjects),
+ baseobjects);
for (i = 0; i < deepest_delta; i++) {
if (!chain_histogram[i])
continue;
- printf("chain length = %d: %lu object%s\n",
- i + 1,
- chain_histogram[i],
- chain_histogram[i] > 1 ? "s" : "");
+ printf_ln(Q_("chain length = %d: %lu object",
+ "chain length = %d: %lu objects",
+ chain_histogram[i]),
+ i + 1,
+ chain_histogram[i]);
}
}
reset_pack_idx_option(&opts);
git_config(git_index_pack_config, &opts);
if (prefix && chdir(prefix))
- die("Cannot come back to cwd");
+ die(_("Cannot come back to cwd"));
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
keep_msg = "";
} else if (!prefixcmp(arg, "--keep=")) {
keep_msg = arg + 7;
+ } else if (!prefixcmp(arg, "--threads=")) {
+ char *end;
+ nr_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || nr_threads < 0)
+ usage(index_pack_usage);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, "
+ "ignoring %s", arg);
+ nr_threads = 1;
+#endif
} else if (!prefixcmp(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
hdr->hdr_signature = htonl(PACK_SIGNATURE);
hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
if (*c != ',')
- die("bad %s", arg);
+ die(_("bad %s"), arg);
hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
if (*c)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
char *c;
opts.version = strtoul(arg + 16, &c, 10);
if (opts.version > 2)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
if (*c == ',')
opts.off32_limit = strtoul(c+1, &c, 0);
if (*c || opts.off32_limit & 0x80000000)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
} else
usage(index_pack_usage);
continue;
if (!pack_name && !from_stdin)
usage(index_pack_usage);
if (fix_thin_pack && !from_stdin)
- die("--fix-thin cannot be used without --stdin");
+ die(_("--fix-thin cannot be used without --stdin"));
if (!index_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
index_name_buf = xmalloc(len);
memcpy(index_name_buf, pack_name, len - 5);
if (keep_msg && !keep_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
keep_name_buf = xmalloc(len);
memcpy(keep_name_buf, pack_name, len - 5);
}
if (verify) {
if (!index_name)
- die("--verify with no packfile name given");
+ die(_("--verify with no packfile name given"));
read_idx_option(&opts, index_name);
opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
}
if (strict)
opts.flags |= WRITE_IDX_STRICT;
+#ifndef NO_PTHREADS
+ if (!nr_threads) {
+ nr_threads = online_cpus();
+ /* An experiment showed that more threads does not mean faster */
+ if (nr_threads > 3)
+ nr_threads = 3;
+ }
+#endif
+
curr_pack = open_pack_file(pack_name);
parse_pack_header();
objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
parse_pack_objects(pack_sha1);
- if (nr_deltas == nr_resolved_deltas) {
- stop_progress(&progress);
- /* Flush remaining pack final 20-byte SHA1. */
- flush();
- } else {
- if (fix_thin_pack) {
- struct sha1file *f;
- unsigned char read_sha1[20], tail_sha1[20];
- char msg[48];
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
- int nr_objects_initial = nr_objects;
- if (nr_unresolved <= 0)
- die("confusion beyond insanity");
- objects = xrealloc(objects,
- (nr_objects + nr_unresolved + 1)
- * sizeof(*objects));
- f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
- sprintf(msg, "completed with %d local objects",
- nr_objects - nr_objects_initial);
- stop_progress_msg(&progress, msg);
- sha1close(f, tail_sha1, 0);
- hashcpy(read_sha1, pack_sha1);
- fixup_pack_header_footer(output_fd, pack_sha1,
- curr_pack, nr_objects,
- read_sha1, consumed_bytes-20);
- if (hashcmp(read_sha1, tail_sha1) != 0)
- die("Unexpected tail checksum for %s "
- "(disk corruption?)", curr_pack);
- }
- if (nr_deltas != nr_resolved_deltas)
- die("pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas);
- }
+ resolve_deltas();
+ conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
check_objects();
#include "string-list.h"
#include "parse-options.h"
#include "branch.h"
+#include "streaming.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
strbuf_release(&out);
}
-static int show_object(const unsigned char *sha1, int show_tag_object,
- struct rev_info *rev)
+static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+{
+ fflush(stdout);
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+}
+
+static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
{
unsigned long size;
enum object_type type;
if (!buf)
return error(_("Could not read object %s"), sha1_to_hex(sha1));
- if (show_tag_object)
- while (offset < size && buf[offset] != '\n') {
- int new_offset = offset + 1;
- while (new_offset < size && buf[new_offset++] != '\n')
- ; /* do nothing */
- if (!prefixcmp(buf + offset, "tagger "))
- show_tagger(buf + offset + 7,
- new_offset - offset - 7, rev);
- offset = new_offset;
- }
+ assert(type == OBJ_TAG);
+ while (offset < size && buf[offset] != '\n') {
+ int new_offset = offset + 1;
+ while (new_offset < size && buf[new_offset++] != '\n')
+ ; /* do nothing */
+ if (!prefixcmp(buf + offset, "tagger "))
+ show_tagger(buf + offset + 7,
+ new_offset - offset - 7, rev);
+ offset = new_offset;
+ }
if (offset < size)
fwrite(buf + offset, size - offset, 1, stdout);
const char *name = objects[i].name;
switch (o->type) {
case OBJ_BLOB:
- ret = show_object(o->sha1, 0, NULL);
+ ret = show_blob_object(o->sha1, NULL);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- ret = show_object(o->sha1, 1, &rev);
+ ret = show_tag_object(o->sha1, &rev);
rev.shown_one = 1;
if (ret)
break;
if (quiet) {
if (!freopen("/dev/null", "w", stderr))
return error("failed to redirect stderr to /dev/null: "
- "%s\n", strerror(errno));
+ "%s", strerror(errno));
}
if (prefix)
if (read_mmfile(mmfs + i, fname))
return -1;
if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
- return error("Cannot merge binary files: %s\n",
+ return error("Cannot merge binary files: %s",
argv[i]);
}
return remote->url_nr;
}
-static void setup_push_upstream(struct remote *remote)
+static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) {
+ /*
+ * There's no point in using shorten_unambiguous_ref here,
+ * as the ambiguity would be on the remote side, not what
+ * we have locally. Plus, this is supposed to be the simple
+ * mode. If the user is doing something crazy like setting
+ * upstream to a non-branch, we should probably be showing
+ * them the big ugly fully qualified ref.
+ */
+ const char *advice_maybe = "";
+ const char *short_upstream =
+ skip_prefix(branch->merge[0]->src, "refs/heads/");
+
+ if (!short_upstream)
+ short_upstream = branch->merge[0]->src;
+ /*
+ * Don't show advice for people who explicitely set
+ * push.default.
+ */
+ if (push_default == PUSH_DEFAULT_UNSPECIFIED)
+ advice_maybe = _("\n"
+ "To choose either option permanently, "
+ "see push.default in 'git help config'.");
+ die(_("The upstream branch of your current branch does not match\n"
+ "the name of your current branch. To push to the upstream branch\n"
+ "on the remote, use\n"
+ "\n"
+ " git push %s HEAD:%s\n"
+ "\n"
+ "To push to the branch of the same name on the remote, use\n"
+ "\n"
+ " git push %s %s\n"
+ "%s"),
+ remote->name, short_upstream,
+ remote->name, branch->name, advice_maybe);
+}
+
+static void setup_push_upstream(struct remote *remote, int simple)
{
struct strbuf refspec = STRBUF_INIT;
struct branch *branch = branch_get(NULL);
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
+ if (simple && strcmp(branch->refname, branch->merge[0]->src))
+ die_push_simple(branch, remote);
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
add_refspec(":");
break;
+ case PUSH_DEFAULT_SIMPLE:
+ setup_push_upstream(remote, 1);
+ break;
+
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote);
+ setup_push_upstream(remote, 0);
break;
case PUSH_DEFAULT_CURRENT:
const char *arg, int unset)
{
int *flags = opt->value;
+
+ if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
+ TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
+ die("%s can only be used once.", opt->long_name);
+
if (arg) {
if (!strcmp(arg, "check"))
*flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+ else if (!strcmp(arg, "on-demand"))
+ *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
else
die("bad %s argument: %s", opt->long_name, arg);
} else
- die("option %s needs an argument (check)", opt->long_name);
+ die("option %s needs an argument (check|on-demand)",
+ opt->long_name);
return 0;
}
static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose]",
- "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
+ "git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>",
"git remote rename <old> <new>",
"git remote rm <name>",
"git remote set-head <name> (-a | -d | <branch>)",
"git remote prune [-n | --dry-run] <name>",
"git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
"git remote set-branches [--add] <name> <branch>...",
- "git remote set-url <name> <newurl> [<oldurl>]",
+ "git remote set-url [--push] <name> <newurl> [<oldurl>]",
"git remote set-url --add <name> <newurl>",
"git remote set-url --delete <name> <url>",
NULL
argv[1] = "-v";
argv[2] = name;
}
- printf("Updating %s\n", name);
+ printf_ln(_("Updating %s"), name);
if (run_command_v_opt(argv, RUN_GIT_CMD))
- return error("Could not fetch %s", name);
+ return error(_("Could not fetch %s"), name);
return 0;
}
}
static const char mirror_advice[] =
-"--mirror is dangerous and deprecated; please\n"
-"\t use --mirror=fetch or --mirror=push instead";
+N_("--mirror is dangerous and deprecated; please\n"
+ "\t use --mirror=fetch or --mirror=push instead");
static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
{
if (not)
*mirror = MIRROR_NONE;
else if (!arg) {
- warning("%s", mirror_advice);
+ warning("%s", _(mirror_advice));
*mirror = MIRROR_BOTH;
}
else if (!strcmp(arg, "fetch"))
else if (!strcmp(arg, "push"))
*mirror = MIRROR_PUSH;
else
- return error("unknown mirror argument: %s", arg);
+ return error(_("unknown mirror argument: %s"), arg);
return 0;
}
usage_with_options(builtin_remote_add_usage, options);
if (mirror && master)
- die("specifying a master branch makes no sense with --mirror");
+ die(_("specifying a master branch makes no sense with --mirror"));
if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
- die("specifying branches to track makes sense only with fetch mirrors");
+ die(_("specifying branches to track makes sense only with fetch mirrors"));
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);
+ die(_("remote %s already exists."), name);
strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
if (!valid_fetch_refspec(buf2.buf))
- die("'%s' is not a valid remote name", name);
+ die(_("'%s' is not a valid remote name"), name);
strbuf_addf(&buf, "remote.%s.url", name);
if (git_config_set(buf.buf, url))
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);
+ return error(_("Could not setup master '%s'"), master);
}
strbuf_release(&buf);
info = item->util;
if (type == REMOTE) {
if (info->remote_name)
- warning("more than one %s", orig_key);
+ warning(_("more than one %s"), orig_key);
info->remote_name = xstrdup(value);
} else if (type == MERGE) {
char *space = strchr(value, ' ');
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
- die("Could not get fetch map for refspec %s",
+ die(_("Could not get fetch map for refspec %s"),
states->remote->fetch_refspec[i]);
states->new.strdup_strings = 1;
states->push.strdup_strings = 1;
if (!remote->push_refspec_nr) {
- item = string_list_append(&states->push, "(matching)");
+ item = string_list_append(&states->push, _("(matching)"));
info = item->util = xcalloc(sizeof(struct push_info), 1);
info->status = PUSH_STATUS_NOTQUERIED;
info->dest = xstrdup(item->string);
for (i = 0; i < remote->push_refspec_nr; i++) {
struct refspec *spec = remote->push + i;
if (spec->matching)
- item = string_list_append(&states->push, "(matching)");
+ item = string_list_append(&states->push, _("(matching)"));
else if (strlen(spec->src))
item = string_list_append(&states->push, spec->src);
else
- item = string_list_append(&states->push, "(delete)");
+ item = string_list_append(&states->push, _("(delete)"));
info = item->util = xcalloc(sizeof(struct push_info), 1);
info->forced = spec->force;
strbuf_addf(&buf, "remote.%s.url", remote->name);
for (i = 0; i < remote->url_nr; i++)
if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->url[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.push", remote->name);
for (i = 0; i < remote->push_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->push_refspec[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", remote->name);
for (i = 0; i < remote->fetch_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->fetch_refspec[i], buf.buf);
if (remote->origin == REMOTE_REMOTES)
path = git_path("remotes/%s", remote->name);
oldremote = remote_get(rename.old);
if (!oldremote)
- die("No such remote: %s", rename.old);
+ die(_("No such remote: %s"), rename.old);
if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
return migrate_file(oldremote);
newremote = remote_get(rename.new);
if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
- die("remote %s already exists.", rename.new);
+ die(_("remote %s already exists."), rename.new);
strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
if (!valid_fetch_refspec(buf.buf))
- die("'%s' is not a valid remote name", rename.new);
+ die(_("'%s' is not a valid remote name"), rename.new);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s", rename.old);
strbuf_addf(&buf2, "remote.%s", rename.new);
if (git_config_rename_section(buf.buf, buf2.buf) < 1)
- return error("Could not rename config section '%s' to '%s'",
+ return error(_("Could not rename config section '%s' to '%s'"),
buf.buf, buf2.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", rename.new);
if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
- return error("Could not remove config section '%s'", buf.buf);
+ return error(_("Could not remove config section '%s'"), buf.buf);
strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
char *ptr;
strlen(rename.old), rename.new,
strlen(rename.new));
} else
- warning("Not updating non-default fetch respec\n"
- "\t%s\n"
- "\tPlease update the configuration manually if necessary.",
+ warning(_("Not updating non-default fetch refspec\n"
+ "\t%s\n"
+ "\tPlease update the configuration manually if necessary."),
buf2.buf);
if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
- return error("Could not append '%s'", buf.buf);
+ return error(_("Could not append '%s'"), buf.buf);
}
read_branches();
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.remote", item->string);
if (git_config_set(buf.buf, rename.new)) {
- return error("Could not set '%s'", buf.buf);
+ return error(_("Could not set '%s'"), buf.buf);
}
}
}
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(item->string, NULL, REF_NODEREF))
- die("deleting '%s' failed", item->string);
+ die(_("deleting '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
strbuf_addf(&buf2, "remote: renamed %s to %s",
item->string, buf.buf);
if (rename_ref(item->string, buf.buf, buf2.buf))
- die("renaming '%s' failed", item->string);
+ die(_("renaming '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
if (create_symref(buf.buf, buf2.buf, buf3.buf))
- die("creating '%s' failed", buf.buf);
+ die(_("creating '%s' failed"), buf.buf);
}
return 0;
}
unsigned char *sha1 = item->util;
if (delete_ref(refname, sha1, 0))
- result |= error("Could not remove branch %s", refname);
+ result |= error(_("Could not remove branch %s"), refname);
}
return result;
}
remote = remote_get(argv[1]);
if (!remote)
- die("No such remote: %s", argv[1]);
+ die(_("No such remote: %s"), argv[1]);
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
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);
+ return error(_("Could not remove config section '%s'"), buf.buf);
read_branches();
for (i = 0; i < branch_list.nr; i++) {
string_list_clear(&branches, 1);
if (skipped.nr) {
- fprintf(stderr, skipped.nr == 1 ?
- "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
- "to delete it, use:\n" :
- "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
- "to delete them, use:\n");
+ fprintf_ln(stderr,
+ Q_("Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+ "to delete it, use:",
+ "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+ "to delete them, use:",
+ skipped.nr));
for (i = 0; i < skipped.nr; i++)
fprintf(stderr, " git branch -d %s\n",
skipped.items[i].string);
states->remote = remote_get(name);
if (!states->remote)
- return error("No such remote: %s", name);
+ return error(_("No such remote: %s"), name);
read_branches();
const char *fmt = "%s";
const char *arg = "";
if (string_list_has_string(&states->new, name)) {
- fmt = " new (next fetch will store in remotes/%s)";
+ fmt = _(" new (next fetch will store in remotes/%s)");
arg = states->remote->name;
} else if (string_list_has_string(&states->tracked, name))
- arg = " tracked";
+ arg = _(" tracked");
else if (string_list_has_string(&states->stale, name))
- arg = " stale (use 'git remote prune' to remove)";
+ arg = _(" stale (use 'git remote prune' to remove)");
else
- arg = " ???";
+ arg = _(" ???");
printf(" %-*s", info->width, name);
printf(fmt, arg);
printf("\n");
int i;
if (branch_info->rebase && branch_info->merge.nr > 1) {
- error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+ error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
item->string);
return 0;
}
printf(" %-*s ", show_info->width, item->string);
if (branch_info->rebase) {
- printf("rebases onto remote %s\n", merge->items[0].string);
+ printf_ln(_("rebases onto remote %s"), merge->items[0].string);
return 0;
} else if (show_info->any_rebase) {
- printf(" merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_(" merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
} else {
- printf("merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_("merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
}
for (i = 1; i < merge->nr; i++)
printf(" %-*s %s %s\n", show_info->width, "", also,
{
struct show_info *show_info = cb_data;
struct push_info *push_info = item->util;
- char *src = item->string, *status = NULL;
+ const char *src = item->string, *status = NULL;
switch (push_info->status) {
case PUSH_STATUS_CREATE:
- status = "create";
+ status = _("create");
break;
case PUSH_STATUS_DELETE:
- status = "delete";
- src = "(none)";
+ status = _("delete");
+ src = _("(none)");
break;
case PUSH_STATUS_UPTODATE:
- status = "up to date";
+ status = _("up to date");
break;
case PUSH_STATUS_FASTFORWARD:
- status = "fast-forwardable";
+ status = _("fast-forwardable");
break;
case PUSH_STATUS_OUTOFDATE:
- status = "local out of date";
+ status = _("local out of date");
break;
case PUSH_STATUS_NOTQUERIED:
break;
}
- if (status)
- printf(" %-*s %s to %-*s (%s)\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- show_info->width2, push_info->dest, status);
- else
- printf(" %-*s %s to %s\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- push_info->dest);
+ if (status) {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ else
+ printf_ln(_(" %-*s pushes to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ } else {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %s"), show_info->width, src,
+ push_info->dest);
+ else
+ printf_ln(_(" %-*s pushes to %s"), show_info->width, src,
+ push_info->dest);
+ }
return 0;
}
get_remote_ref_states(*argv, &states, query_flag);
- printf("* remote %s\n", *argv);
- printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ?
- states.remote->url[0] : "(no URL)");
+ printf_ln(_("* remote %s"), *argv);
+ printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ?
+ states.remote->url[0] : _("(no URL)"));
if (states.remote->pushurl_nr) {
url = states.remote->pushurl;
url_nr = states.remote->pushurl_nr;
url_nr = states.remote->url_nr;
}
for (i = 0; i < url_nr; i++)
- printf(" Push URL: %s\n", url[i]);
+ printf_ln(_(" Push URL: %s"), url[i]);
if (!i)
- printf(" Push URL: %s\n", "(no URL)");
+ printf_ln(_(" Push URL: %s"), "(no URL)");
if (no_query)
- printf(" HEAD branch: (not queried)\n");
+ printf_ln(_(" HEAD branch: %s"), "(not queried)");
else if (!states.heads.nr)
- printf(" HEAD branch: (unknown)\n");
+ printf_ln(_(" HEAD branch: %s"), "(unknown)");
else if (states.heads.nr == 1)
- printf(" HEAD branch: %s\n", states.heads.items[0].string);
+ printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string);
else {
- printf(" HEAD branch (remote HEAD is ambiguous,"
- " may be one of the following):\n");
+ printf(_(" HEAD branch (remote HEAD is ambiguous,"
+ " may be one of the following):\n"));
for (i = 0; i < states.heads.nr; i++)
printf(" %s\n", states.heads.items[i].string);
}
for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
for_each_string_list(&states.stale, add_remote_to_show_info, &info);
if (info.list->nr)
- printf(" Remote branch%s:%s\n",
- info.list->nr > 1 ? "es" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Remote branch:%s",
+ " Remote branches:%s",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_remote_info_item, &info);
string_list_clear(info.list, 0);
info.any_rebase = 0;
for_each_string_list(&branch_list, add_local_to_show_info, &info);
if (info.list->nr)
- printf(" Local branch%s configured for 'git pull':\n",
- info.list->nr > 1 ? "es" : "");
+ printf_ln(Q_(" Local branch configured for 'git pull':",
+ " Local branches configured for 'git pull':",
+ info.list->nr));
for_each_string_list(info.list, show_local_info_item, &info);
string_list_clear(info.list, 0);
/* git push info */
if (states.remote->mirror)
- printf(" Local refs will be mirrored by 'git push'\n");
+ printf_ln(_(" Local refs will be mirrored by 'git push'"));
info.width = info.width2 = 0;
for_each_string_list(&states.push, add_push_to_show_info, &info);
qsort(info.list->items, info.list->nr,
sizeof(*info.list->items), cmp_string_with_push);
if (info.list->nr)
- printf(" Local ref%s configured for 'git push'%s:\n",
- info.list->nr > 1 ? "s" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Local ref configured for 'git push'%s:",
+ " Local refs configured for 'git push'%s:",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_push_info_item, &info);
string_list_clear(info.list, 0);
memset(&states, 0, sizeof(states));
get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
if (!states.heads.nr)
- result |= error("Cannot determine remote HEAD");
+ result |= error(_("Cannot determine remote HEAD"));
else if (states.heads.nr > 1) {
- result |= error("Multiple remote HEAD branches. "
- "Please choose one explicitly with:");
+ result |= error(_("Multiple remote HEAD branches. "
+ "Please choose one explicitly with:"));
for (i = 0; i < states.heads.nr; i++)
fprintf(stderr, " git remote set-head %s %s\n",
argv[0], states.heads.items[i].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
if (delete_ref(buf.buf, NULL, REF_NODEREF))
- result |= error("Could not delete %s", buf.buf);
+ result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
if (!ref_exists(buf2.buf))
- result |= error("Not a valid ref: %s", buf2.buf);
+ result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
- result |= error("Could not setup %s", buf.buf);
+ result |= error(_("Could not setup %s"), buf.buf);
if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
int result = 0, i;
struct ref_states states;
const char *dangling_msg = dry_run
- ? " %s will become dangling!\n"
- : " %s has become dangling!\n";
+ ? _(" %s will become dangling!")
+ : _(" %s has become dangling!");
memset(&states, 0, sizeof(states));
get_remote_ref_states(remote, &states, GET_REF_STATES);
if (states.stale.nr) {
- printf("Pruning %s\n", remote);
- printf("URL: %s\n",
+ printf_ln(_("Pruning %s"), remote);
+ printf_ln(_("URL: %s"),
states.remote->url_nr
? states.remote->url[0]
- : "(no URL)");
+ : _("(no URL)"));
}
for (i = 0; i < states.stale.nr; i++) {
if (!dry_run)
result |= delete_ref(refname, NULL, 0);
- printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
- abbrev_ref(refname, "refs/remotes/"));
+ if (dry_run)
+ printf_ln(_(" * [would prune] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
+ else
+ printf_ln(_(" * [pruned] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
warn_dangling_symref(stdout, dangling_msg, refname);
}
strbuf_addf(&key, "remote.%s.fetch", remotename);
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
argc = parse_options(argc, argv, NULL, options,
builtin_remote_setbranches_usage, 0);
if (argc == 0) {
- error("no remote specified");
+ error(_("no remote specified"));
usage_with_options(builtin_remote_setbranches_usage, options);
}
argv[argc] = NULL;
PARSE_OPT_KEEP_ARGV0);
if (add_mode && delete_mode)
- die("--add --delete doesn't make sense");
+ die(_("--add --delete doesn't make sense"));
if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
usage_with_options(builtin_remote_seturl_usage, options);
oldurl = newurl;
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (push_mode) {
/* Old URL specified. Demand that one matches. */
if (regcomp(&old_regex, oldurl, REG_EXTENDED))
- die("Invalid old URL pattern: %s", oldurl);
+ die(_("Invalid old URL pattern: %s"), oldurl);
for (i = 0; i < urlset_nr; i++)
if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
else
negative_matches++;
if (!delete_mode && !matches)
- die("No such URL found: %s", oldurl);
+ die(_("No such URL found: %s"), oldurl);
if (delete_mode && !negative_matches && !push_mode)
- die("Will not delete all non-push URLs");
+ die(_("Will not delete all non-push URLs"));
regfree(&old_regex);
else if (!strcmp(argv[0], "update"))
result = update(argc, argv);
else {
- error("Unknown subcommand: %s", argv[0]);
+ error(_("Unknown subcommand: %s"), argv[0]);
usage_with_options(builtin_remote_usage, options);
}
OPT_END(),
OPT_END(),
OPT_END(),
+ OPT_END(),
+ OPT_END(),
};
if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
+ OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
+ OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"),
OPT_END(),
};
if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
"--abort", rollback,
NULL);
+ /* implies allow_empty */
+ if (opts->keep_redundant_commits)
+ opts->allow_empty = 1;
+
/* Set the subcommand */
if (remove_state)
opts->subcommand = REPLAY_REMOVE_STATE;
#include "revision.h"
#include "gpg-interface.h"
#include "sha1-array.h"
+#include "column.h"
static const char * const git_tag_usage[] = {
"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
};
static struct sha1_array points_at;
+static unsigned int colopts;
static int match_pattern(const char **patterns, const char *ref)
{
int status = git_gpg_config(var, value, cb);
if (status)
return status;
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "tag", &colopts);
return git_default_config(var, value, cb);
}
OPT_STRING('u', "local-user", &keyid, "key-id",
"use another key to sign the tag"),
OPT__FORCE(&force, "replace the tag if exists"),
+ OPT_COLUMN(0, "column", &colopts, "show tag list in columns"),
OPT_GROUP("Tag listing options"),
{
if (list + delete + verify > 1)
usage_with_options(git_tag_usage, options);
- if (list)
- return list_tags(argv, lines == -1 ? 0 : lines,
- with_commit);
+ finalize_colopts(&colopts, -1);
+ if (list && lines != -1) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and -n are incompatible"));
+ colopts = 0;
+ }
+ if (list) {
+ int ret;
+ if (column_active(colopts)) {
+ struct column_options copts;
+ memset(&copts, 0, sizeof(copts));
+ copts.padding = 2;
+ run_column_filter(colopts, &copts);
+ }
+ ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+ if (column_active(colopts))
+ stop_column_filter();
+ return ret;
+ }
if (lines != -1)
die(_("-n option is only allowed with -l."));
if (with_commit)
if (stream.total_out == size && ret == Z_STREAM_END)
break;
if (ret != Z_OK) {
- error("inflate returned %d\n", ret);
+ error("inflate returned %d", ret);
free(buf);
buf = NULL;
if (!recover)
int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
+ int preferred_index_format = 0;
char set_executable_bit = 0;
struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
"(for porcelains) forget saved unresolved conflicts",
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
resolve_undo_clear_callback},
+ OPT_INTEGER(0, "index-version", &preferred_index_format,
+ "write index in this format"),
OPT_END()
};
}
}
argc = parse_options_end(&ctx);
+ if (preferred_index_format) {
+ if (preferred_index_format < INDEX_FORMAT_LB ||
+ INDEX_FORMAT_UB < preferred_index_format)
+ die("index-version %d not in range: %d..%d",
+ preferred_index_format,
+ INDEX_FORMAT_LB, INDEX_FORMAT_UB);
+
+ if (the_index.version != preferred_index_format)
+ active_cache_changed = 1;
+ the_index.version = preferred_index_format;
+ }
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
OPT_END()
};
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
update_server_info_usage, 0);
if (argc > 0)
if (strbuf_getwholeline_fd(&buf, fd, '\n') ||
strcmp(buf.buf, bundle_signature)) {
if (report_path)
- error("'%s' does not look like a v2 bundle file",
+ error(_("'%s' does not look like a v2 bundle file"),
report_path);
status = -1;
goto abort;
(40 <= buf.len && !isspace(buf.buf[40])) ||
(!is_prereq && buf.len <= 40)) {
if (report_path)
- error("unrecognized header: %s%s (%d)",
+ error(_("unrecognized header: %s%s (%d)"),
(is_prereq ? "-" : ""), buf.buf, (int)buf.len);
status = -1;
break;
int fd = open(path, O_RDONLY);
if (fd < 0)
- return error("could not open '%s'", path);
+ return error(_("could not open '%s'"), path);
return parse_bundle_header(fd, header, path);
}
struct object_array refs;
struct commit *commit;
int i, ret = 0, req_nr;
- const char *message = "Repository lacks these prerequisite commits:";
+ const char *message = _("Repository lacks these prerequisite commits:");
init_revisions(&revs, NULL);
for (i = 0; i < p->nr; i++) {
revs.leak_pending = 1;
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
i = req_nr;
while (i && (commit = get_revision(&revs)))
struct ref_list *r;
r = &header->references;
- printf("The bundle contains %d ref%s\n",
- r->nr, (1 < r->nr) ? "s" : "");
+ printf_ln(Q_("The bundle contains %d ref",
+ "The bundle contains %d refs",
+ r->nr),
+ r->nr);
list_refs(r, 0, NULL);
r = &header->prerequisites;
- printf("The bundle requires these %d ref%s\n",
- r->nr, (1 < r->nr) ? "s" : "");
+ printf_ln(Q_("The bundle requires this ref",
+ "The bundle requires these %d refs",
+ r->nr),
+ r->nr);
list_refs(r, 0, NULL);
}
return ret;
strbuf_release(&buf);
fclose(rls_fout);
if (finish_command(&rls))
- return error("rev-list died");
+ return error(_("rev-list died"));
/* write references */
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
- return error("unrecognized argument: %s", argv[1]);
+ return error(_("unrecognized argument: %s"), argv[1]);
object_array_remove_duplicates(&revs.pending);
* constraints.
*/
if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
- warning("ref '%s' is excluded by the rev-list options",
+ warning(_("ref '%s' is excluded by the rev-list options"),
e->name);
free(ref);
continue;
free(ref);
}
if (!ref_count)
- die ("Refusing to create empty bundle.");
+ die(_("Refusing to create empty bundle."));
/* end header */
write_or_die(bundle_fd, "\n", 1);
rls.out = bundle_fd;
rls.git_cmd = 1;
if (start_command(&rls))
- return error("Could not spawn pack-objects");
+ return error(_("Could not spawn pack-objects"));
/*
* start_command closed bundle_fd if it was > 1
}
close(rls.in);
if (finish_command(&rls))
- return error ("pack-objects died");
+ return error(_("pack-objects died"));
if (!bundle_to_stdout) {
if (commit_lock_file(&lock))
- die_errno("cannot create '%s'", path);
+ die_errno(_("cannot create '%s'"), path);
}
return 0;
}
ip.no_stdout = 1;
ip.git_cmd = 1;
if (run_command(&ip))
- return error("index-pack died");
+ return error(_("index-pack died"));
return 0;
}
unsigned int hdr_entries;
};
+#define INDEX_FORMAT_LB 2
+#define INDEX_FORMAT_UB 4
+
/*
* The "cache_time" is just the low 32 bits of the
* time. It doesn't matter if it overflows - we only
unsigned int nsec;
};
-/*
- * dev/ino/uid/gid/size are also just tracked to the low 32 bits
- * Again - this is just a (very strong in practice) heuristic that
- * the inode hasn't changed.
- *
- * We save the fields in big-endian order to allow using the
- * index file over NFS transparently.
- */
-struct ondisk_cache_entry {
- struct cache_time ctime;
- struct cache_time mtime;
- unsigned int dev;
- unsigned int ino;
- unsigned int mode;
- unsigned int uid;
- unsigned int gid;
- unsigned int size;
- unsigned char sha1[20];
- unsigned short flags;
- char name[FLEX_ARRAY]; /* more */
-};
-
-/*
- * This struct is used when CE_EXTENDED bit is 1
- * The struct must match ondisk_cache_entry exactly from
- * ctime till flags
- */
-struct ondisk_cache_entry_extended {
- struct cache_time ctime;
- struct cache_time mtime;
- unsigned int dev;
- unsigned int ino;
- unsigned int mode;
- unsigned int uid;
- unsigned int gid;
- unsigned int size;
- unsigned char sha1[20];
- unsigned short flags;
- unsigned short flags2;
- char name[FLEX_ARRAY]; /* more */
-};
-
struct cache_entry {
struct cache_time ce_ctime;
struct cache_time ce_mtime;
}
#define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
- ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
- ondisk_cache_entry_size(ce_namelen(ce)))
#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
return S_IFGITLINK;
}
-#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
#define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
-#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
-#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
struct index_state {
struct cache_entry **cache;
+ unsigned int version;
unsigned int cache_nr, cache_alloc, cache_changed;
struct string_list *resolve_undo;
struct cache_tree *cache_tree;
enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
+ PUSH_DEFAULT_SIMPLE,
PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT,
PUSH_DEFAULT_UNSPECIFIED
};
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
-const char *show_date_relative(unsigned long time, int tz,
- const struct timeval *now,
- char *timebuf,
- size_t timebuf_size);
+void show_date_relative(unsigned long time, int tz, const struct timeval *now,
+ struct strbuf *timebuf);
int parse_date(const char *date, char *buf, int bufsize);
int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
void datestamp(char *buf, int bufsize);
--- /dev/null
+#include "cache.h"
+#include "column.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "utf8.h"
+
+#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
+ (x) * (d)->rows + (y) : \
+ (y) * (d)->cols + (x))
+
+struct column_data {
+ const struct string_list *list;
+ unsigned int colopts;
+ struct column_options opts;
+
+ int rows, cols;
+ int *len; /* cell length */
+ int *width; /* index to the longest row in column */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(unsigned int colopts, const char *s)
+{
+ int len, i = 0;
+ struct strbuf str = STRBUF_INIT;
+
+ strbuf_addstr(&str, s);
+ while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+ int len = strspn(s + 2, "0123456789;");
+ i = s - str.buf;
+ strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+ }
+ len = utf8_strwidth(str.buf);
+ strbuf_release(&str);
+ return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(struct column_data *data, int *width)
+{
+ int i;
+
+ *width = 0;
+ for (i = 0; i < data->list->nr; i++)
+ if (*width < data->len[i])
+ *width = data->len[i];
+
+ *width += data->opts.padding;
+
+ data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
+ if (data->cols == 0)
+ data->cols = 1;
+
+ data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
+
+static void compute_column_width(struct column_data *data)
+{
+ int i, x, y;
+ for (x = 0; x < data->cols; x++) {
+ data->width[x] = XY2LINEAR(data, x, 0);
+ for (y = 0; y < data->rows; y++) {
+ i = XY2LINEAR(data, x, y);
+ if (i < data->list->nr &&
+ data->len[data->width[x]] < data->len[i])
+ data->width[x] = i;
+ }
+ }
+}
+
+/*
+ * Shrink all columns by shortening them one row each time (and adding
+ * more columns along the way). Hopefully the longest cell will be
+ * moved to the next column, column is shrunk so we have more space
+ * for new columns. The process ends when the whole thing no longer
+ * fits in data->total_width.
+ */
+static void shrink_columns(struct column_data *data)
+{
+ data->width = xrealloc(data->width,
+ sizeof(*data->width) * data->cols);
+ while (data->rows > 1) {
+ int x, total_width, cols, rows;
+ rows = data->rows;
+ cols = data->cols;
+
+ data->rows--;
+ data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
+ if (data->cols != cols)
+ data->width = xrealloc(data->width,
+ sizeof(*data->width) * data->cols);
+ compute_column_width(data);
+
+ total_width = strlen(data->opts.indent);
+ for (x = 0; x < data->cols; x++) {
+ total_width += data->len[data->width[x]];
+ total_width += data->opts.padding;
+ }
+ if (total_width > data->opts.width) {
+ data->rows = rows;
+ data->cols = cols;
+ break;
+ }
+ }
+ compute_column_width(data);
+}
+
+/* Display without layout when not enabled */
+static void display_plain(const struct string_list *list,
+ const char *indent, const char *nl)
+{
+ int i;
+
+ for (i = 0; i < list->nr; i++)
+ printf("%s%s%s", indent, list->items[i].string, nl);
+}
+
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+ const char *empty_cell, int x, int y)
+{
+ int i, len, newline;
+
+ i = XY2LINEAR(data, x, y);
+ if (i >= data->list->nr)
+ return -1;
+
+ len = data->len[i];
+ if (data->width && data->len[data->width[x]] < initial_width) {
+ /*
+ * empty_cell has initial_width chars, if real column
+ * is narrower, increase len a bit so we fill less
+ * space.
+ */
+ len += initial_width - data->len[data->width[x]];
+ len -= data->opts.padding;
+ }
+
+ if (COL_LAYOUT(data->colopts) == COL_COLUMN)
+ newline = i + data->rows >= data->list->nr;
+ else
+ newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+ printf("%s%s%s",
+ x == 0 ? data->opts.indent : "",
+ data->list->items[i].string,
+ newline ? data->opts.nl : empty_cell + len);
+ return 0;
+}
+
+/* Display COL_COLUMN or COL_ROW */
+static void display_table(const struct string_list *list,
+ unsigned int colopts,
+ const struct column_options *opts)
+{
+ struct column_data data;
+ int x, y, i, initial_width;
+ char *empty_cell;
+
+ memset(&data, 0, sizeof(data));
+ data.list = list;
+ data.colopts = colopts;
+ data.opts = *opts;
+
+ data.len = xmalloc(sizeof(*data.len) * list->nr);
+ for (i = 0; i < list->nr; i++)
+ data.len[i] = item_length(colopts, list->items[i].string);
+
+ layout(&data, &initial_width);
+
+ if (colopts & COL_DENSE)
+ shrink_columns(&data);
+
+ empty_cell = xmalloc(initial_width + 1);
+ memset(empty_cell, ' ', initial_width);
+ empty_cell[initial_width] = '\0';
+ for (y = 0; y < data.rows; y++) {
+ for (x = 0; x < data.cols; x++)
+ if (display_cell(&data, initial_width, empty_cell, x, y))
+ break;
+ }
+
+ free(data.len);
+ free(data.width);
+ free(empty_cell);
+}
+
+void print_columns(const struct string_list *list, unsigned int colopts,
+ const struct column_options *opts)
+{
+ struct column_options nopts;
+
+ if (!list->nr)
+ return;
+ assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
+
+ memset(&nopts, 0, sizeof(nopts));
+ nopts.indent = opts && opts->indent ? opts->indent : "";
+ nopts.nl = opts && opts->nl ? opts->nl : "\n";
+ nopts.padding = opts ? opts->padding : 1;
+ nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
+ if (!column_active(colopts)) {
+ display_plain(list, "", "\n");
+ return;
+ }
+ switch (COL_LAYOUT(colopts)) {
+ case COL_PLAIN:
+ display_plain(list, nopts.indent, nopts.nl);
+ break;
+ case COL_ROW:
+ case COL_COLUMN:
+ display_table(list, colopts, &nopts);
+ break;
+ default:
+ die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
+ }
+}
+
+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
+{
+ if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
+ if (stdout_is_tty < 0)
+ stdout_is_tty = isatty(1);
+ *colopts &= ~COL_ENABLE_MASK;
+ if (stdout_is_tty)
+ *colopts |= COL_ENABLED;
+ }
+ return 0;
+}
+
+struct colopt {
+ const char *name;
+ unsigned int value;
+ unsigned int mask;
+};
+
+#define LAYOUT_SET 1
+#define ENABLE_SET 2
+
+static int parse_option(const char *arg, int len, unsigned int *colopts,
+ int *group_set)
+{
+ struct colopt opts[] = {
+ { "always", COL_ENABLED, COL_ENABLE_MASK },
+ { "never", COL_DISABLED, COL_ENABLE_MASK },
+ { "auto", COL_AUTO, COL_ENABLE_MASK },
+ { "plain", COL_PLAIN, COL_LAYOUT_MASK },
+ { "column", COL_COLUMN, COL_LAYOUT_MASK },
+ { "row", COL_ROW, COL_LAYOUT_MASK },
+ { "dense", COL_DENSE, 0 },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(opts); i++) {
+ int set = 1, arg_len = len, name_len;
+ const char *arg_str = arg;
+
+ if (!opts[i].mask) {
+ if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
+ arg_str += 2;
+ arg_len -= 2;
+ set = 0;
+ }
+ }
+
+ name_len = strlen(opts[i].name);
+ if (arg_len != name_len ||
+ strncmp(arg_str, opts[i].name, name_len))
+ continue;
+
+ switch (opts[i].mask) {
+ case COL_ENABLE_MASK:
+ *group_set |= ENABLE_SET;
+ break;
+ case COL_LAYOUT_MASK:
+ *group_set |= LAYOUT_SET;
+ break;
+ }
+
+ if (opts[i].mask)
+ *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
+ else {
+ if (set)
+ *colopts |= opts[i].value;
+ else
+ *colopts &= ~opts[i].value;
+ }
+ return 0;
+ }
+
+ return error("unsupported option '%s'", arg);
+}
+
+static int parse_config(unsigned int *colopts, const char *value)
+{
+ const char *sep = " ,";
+ int group_set = 0;
+
+ while (*value) {
+ int len = strcspn(value, sep);
+ if (len) {
+ if (parse_option(value, len, colopts, &group_set))
+ return -1;
+
+ value += len;
+ }
+ value += strspn(value, sep);
+ }
+ /*
+ * Setting layout implies "always" if neither always, never
+ * nor auto is specified.
+ *
+ * Current value in COL_ENABLE_MASK is disregarded. This means if
+ * you set column.ui = auto and pass --column=row, then "auto"
+ * will become "always".
+ */
+ if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
+ *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ return 0;
+}
+
+static int column_config(const char *var, const char *value,
+ const char *key, unsigned int *colopts)
+{
+ if (!value)
+ return config_error_nonbool(var);
+ if (parse_config(colopts, value))
+ return error("invalid column.%s mode %s", key, value);
+ return 0;
+}
+
+int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts)
+{
+ const char *it = skip_prefix(var, "column.");
+ if (!it)
+ return 0;
+
+ if (!strcmp(it, "ui"))
+ return column_config(var, value, "ui", colopts);
+
+ if (command && !strcmp(it, command))
+ return column_config(var, value, it, colopts);
+
+ return 0;
+}
+
+int parseopt_column_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ unsigned int *colopts = opt->value;
+ *colopts |= COL_PARSEOPT;
+ *colopts &= ~COL_ENABLE_MASK;
+ if (unset) /* --no-column == never */
+ return 0;
+ /* --column == always unless "arg" states otherwise */
+ *colopts |= COL_ENABLED;
+ if (arg)
+ return parse_config(colopts, arg);
+
+ return 0;
+}
+
+static int fd_out = -1;
+static struct child_process column_process;
+
+int run_column_filter(int colopts, const struct column_options *opts)
+{
+ const char *av[10];
+ int ret, ac = 0;
+ struct strbuf sb_colopt = STRBUF_INIT;
+ struct strbuf sb_width = STRBUF_INIT;
+ struct strbuf sb_padding = STRBUF_INIT;
+
+ if (fd_out != -1)
+ return -1;
+
+ av[ac++] = "column";
+ strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts);
+ av[ac++] = sb_colopt.buf;
+ if (opts && opts->width) {
+ strbuf_addf(&sb_width, "--width=%d", opts->width);
+ av[ac++] = sb_width.buf;
+ }
+ if (opts && opts->indent) {
+ av[ac++] = "--indent";
+ av[ac++] = opts->indent;
+ }
+ if (opts && opts->padding) {
+ strbuf_addf(&sb_padding, "--padding=%d", opts->padding);
+ av[ac++] = sb_padding.buf;
+ }
+ av[ac] = NULL;
+
+ fflush(stdout);
+ memset(&column_process, 0, sizeof(column_process));
+ column_process.in = -1;
+ column_process.out = dup(1);
+ column_process.git_cmd = 1;
+ column_process.argv = av;
+
+ ret = start_command(&column_process);
+
+ strbuf_release(&sb_colopt);
+ strbuf_release(&sb_width);
+ strbuf_release(&sb_padding);
+
+ if (ret)
+ return -2;
+
+ fd_out = dup(1);
+ close(1);
+ dup2(column_process.in, 1);
+ close(column_process.in);
+ return 0;
+}
+
+int stop_column_filter(void)
+{
+ if (fd_out == -1)
+ return -1;
+
+ fflush(stdout);
+ close(1);
+ finish_command(&column_process);
+ dup2(fd_out, 1);
+ close(fd_out);
+ fd_out = -1;
+ return 0;
+}
--- /dev/null
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#define COL_LAYOUT_MASK 0x000F
+#define COL_ENABLE_MASK 0x0030 /* always, never or auto */
+#define COL_PARSEOPT 0x0040 /* --column is given from cmdline */
+#define COL_DENSE 0x0080 /* Shrink columns when possible,
+ making space for more columns */
+
+#define COL_DISABLED 0x0000 /* must be zero */
+#define COL_ENABLED 0x0010
+#define COL_AUTO 0x0020
+
+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
+#define COL_COLUMN 0 /* Fill columns before rows */
+#define COL_ROW 1 /* Fill rows before columns */
+#define COL_PLAIN 15 /* one column */
+
+#define explicitly_enable_column(c) \
+ (((c) & COL_PARSEOPT) && column_active(c))
+
+struct column_options {
+ int width;
+ int padding;
+ const char *indent;
+ const char *nl;
+};
+
+struct option;
+extern int parseopt_column_callback(const struct option *, const char *, int);
+extern int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts);
+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
+static inline int column_active(unsigned int colopts)
+{
+ return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
+}
+
+extern void print_columns(const struct string_list *list, unsigned int colopts,
+ const struct column_options *opts);
+
+extern int run_column_filter(int colopts, const struct column_options *);
+extern int stop_column_filter(void);
+
+#endif
git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain common
+git-column purehelpers
git-commit mainporcelain common
git-commit-tree plumbingmanipulators
git-config ancillarymanipulators
git-mv mainporcelain common
git-name-rev plumbinginterrogators
git-notes mainporcelain
+git-p4 foreignscminterface
git-pack-objects plumbingmanipulators
git-pack-redundant plumbinginterrogators
git-pack-refs ancillarymanipulators
#include "revision.h"
#include "notes.h"
#include "gpg-interface.h"
+#include "mergesort.h"
int save_commit_buffer = 1;
return commit_list_insert(item, pp);
}
+static int commit_list_compare_by_date(const void *a, const void *b)
+{
+ unsigned long a_date = ((const struct commit_list *)a)->item->date;
+ unsigned long b_date = ((const struct commit_list *)b)->item->date;
+ if (a_date < b_date)
+ return 1;
+ if (a_date > b_date)
+ return -1;
+ return 0;
+}
+
+static void *commit_list_get_next(const void *a)
+{
+ return ((const struct commit_list *)a)->next;
+}
+
+static void commit_list_set_next(void *a, void *next)
+{
+ ((struct commit_list *)a)->next = next;
+}
void commit_list_sort_by_date(struct commit_list **list)
{
- struct commit_list *ret = NULL;
- while (*list) {
- commit_list_insert_by_date((*list)->item, &ret);
- *list = (*list)->next;
- }
- *list = ret;
+ *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
+ commit_list_compare_by_date);
}
struct commit *pop_most_recent_commit(struct commit_list **list,
}
return commit;
}
+
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ * struct commit_list *list;
+ * struct commit_list **next = &list;
+ *
+ * next = commit_list_append(c1, next);
+ * next = commit_list_append(c2, next);
+ * assert(commit_list_count(list) == 2);
+ * return list;
+ */
+struct commit_list **commit_list_append(struct commit *commit,
+ struct commit_list **next)
+{
+ struct commit_list *new = xmalloc(sizeof(struct commit_list));
+ new->item = commit;
+ *next = new;
+ new->next = NULL;
+ return &new->next;
+}
struct commit_list *commit_list_insert(struct commit *item,
struct commit_list **list);
+struct commit_list **commit_list_append(struct commit *commit,
+ struct commit_list **next);
unsigned commit_list_count(const struct commit_list *l);
struct commit_list *commit_list_insert_by_date(struct commit *item,
struct commit_list **list);
#define S_IWOTH 0
#define S_IXOTH 0
#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
-#define S_ISUID 0
-#define S_ISGID 0
-#define S_ISVTX 0
+
+#define S_ISUID 0004000
+#define S_ISGID 0002000
+#define S_ISVTX 0001000
#define WIFEXITED(x) 1
#define WIFSIGNALED(x) 0
return (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0;
}
+static inline int pthread_key_delete(pthread_key_t key)
+{
+ return TlsFree(key) ? 0 : EINVAL;
+}
+
static inline int pthread_setspecific(pthread_key_t key, const void *value)
{
return TlsSetValue(key, (void *)value) ? 0 : EINVAL;
temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
if (!CloseHandle(hmap))
- warning("unable to close file mapping handle\n");
+ warning("unable to close file mapping handle");
return temp ? temp : MAP_FAILED;
}
push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
+ else if (!strcmp(value, "simple"))
+ push_default = PUSH_DEFAULT_SIMPLE;
else if (!strcmp(value, "upstream"))
push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "tracking")) /* deprecated */
push_default = PUSH_DEFAULT_CURRENT;
else {
error("Malformed value for %s: %s", var, value);
- return error("Must be one of nothing, matching, "
- "tracking or current.");
+ return error("Must be one of nothing, matching, simple, "
+ "upstream or current.");
}
return 0;
}
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.59)
-AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
-
-AC_CONFIG_SRCDIR([git.c])
-
-config_file=config.mak.autogen
-config_append=config.mak.append
-config_in=config.mak.in
-
-echo "# ${config_append}. Generated by configure." > "${config_append}"
-
+## Definitions of private macros.
-## Definitions of macros
# GIT_CONF_APPEND_LINE(LINE)
# --------------------------
# Append LINE to file ${config_append}
AC_DEFUN([GIT_CONF_APPEND_LINE],
-[echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE
-#
+ [echo "$1" >> "${config_append}"])
+
# GIT_ARG_SET_PATH(PROGRAM)
# -------------------------
# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
# Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM version used.
AC_DEFUN([GIT_ARG_SET_PATH],
-[AC_ARG_WITH([$1],
- [AS_HELP_STRING([--with-$1=PATH],
- [provide PATH to $1])],
- [GIT_CONF_APPEND_PATH($1,$2)],[])
-])# GIT_ARG_SET_PATH
-#
+ [AC_ARG_WITH([$1],
+ [AS_HELP_STRING([--with-$1=PATH],
+ [provide PATH to $1])],
+ [GIT_CONF_APPEND_PATH([$1], [$2])],
+ [])])
+
# GIT_CONF_APPEND_PATH(PROGRAM)
-# ------------------------------
+# -----------------------------
# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
# Used by GIT_ARG_SET_PATH(PROGRAM)
# Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM is used.
AC_DEFUN([GIT_CONF_APPEND_PATH],
-[PROGRAM=m4_toupper($1); \
-if test "$withval" = "no"; then \
- if test -n "$2"; then \
- m4_toupper($1)_PATH=$withval; \
- AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \
- GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \
- GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \
- else \
- AC_MSG_ERROR([You cannot use git without $1]); \
- fi; \
-else \
- if test "$withval" = "yes"; then \
- AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
- else \
- m4_toupper($1)_PATH=$withval; \
- AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \
- GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \
- fi; \
-fi; \
-]) # GIT_CONF_APPEND_PATH
-#
+ [m4_pushdef([GIT_UC_PROGRAM], m4_toupper([$1]))dnl
+ PROGRAM=GIT_UC_PROGRAM
+ if test "$withval" = "no"; then
+ if test -n "$2"; then
+ GIT_UC_PROGRAM[]_PATH=$withval
+ AC_MSG_NOTICE([Disabling use of ${PROGRAM}])
+ GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease)
+ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=)
+ else
+ AC_MSG_ERROR([You cannot use git without $1])
+ fi
+ else
+ if test "$withval" = "yes"; then
+ AC_MSG_WARN([You should provide path for --with-$1=PATH])
+ else
+ GIT_UC_PROGRAM[]_PATH=$withval
+ AC_MSG_NOTICE([Setting GIT_UC_PROGRAM[]_PATH to $withval])
+ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval)
+ fi
+ fi
+ m4_popdef([GIT_UC_PROGRAM])])
+
# GIT_PARSE_WITH(PACKAGE)
# -----------------------
# For use in AC_ARG_WITH action-if-found, for packages default ON.
# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH
# * Unset NO_PACKAGE for --with-PACKAGE without ARG
AC_DEFUN([GIT_PARSE_WITH],
-[PACKAGE=m4_toupper($1); \
-if test "$withval" = "no"; then \
- m4_toupper(NO_$1)=YesPlease; \
-elif test "$withval" = "yes"; then \
- m4_toupper(NO_$1)=; \
-else \
- m4_toupper(NO_$1)=; \
- m4_toupper($1)DIR=$withval; \
- AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \
- GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
-fi \
-])# GIT_PARSE_WITH
-#
+ [m4_pushdef([GIT_UC_PACKAGE], m4_toupper([$1]))dnl
+ PACKAGE=GIT_UC_PACKAGE
+ if test "$withval" = "no"; then
+ NO_[]GIT_UC_PACKAGE=YesPlease
+ elif test "$withval" = "yes"; then
+ NO_[]GIT_UC_PACKAGE=
+ else
+ NO_[]GIT_UC_PACKAGE=
+ GIT_UC_PACKAGE[]DIR=$withval
+ AC_MSG_NOTICE([Setting GIT_UC_PACKAGE[]DIR to $withval])
+ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval)
+ fi
+ m4_popdef([GIT_UC_PACKAGE])])
+
# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
-# ---------------------
+# -----------------------------------------------------
# Set VAR to the value specied by --with-WITHNAME.
# No verification of arguments is performed, but warnings are issued
# if either 'yes' or 'no' is specified.
AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
[AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-$1=VALUE], $3)],
- if test -n "$withval"; then \
- if test "$withval" = "yes" -o "$withval" = "no"; then \
+ if test -n "$withval"; then
+ if test "$withval" = "yes" -o "$withval" = "no"; then
AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
- [a value for $1 ($2). Maybe you do...?]); \
- fi; \
- \
- AC_MSG_NOTICE([Setting $2 to $withval]); \
- GIT_CONF_APPEND_LINE($2=$withval); \
+ [a value for $1 ($2). Maybe you do...?])
+ fi
+ AC_MSG_NOTICE([Setting $2 to $withval])
+ GIT_CONF_APPEND_LINE($2=$withval)
fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
-dnl
-dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
-dnl -----------------------------------------
-dnl Similar to AC_CHECK_FUNC, but on systems that do not generate
-dnl warnings for missing prototypes (e.g. FreeBSD when compiling without
-dnl -Wall), it does not work. By looking for function definition in
-dnl libraries, this problem can be worked around.
+#
+# GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
+# -----------------------------------------
+# Similar to AC_CHECK_FUNC, but on systems that do not generate
+# warnings for missing prototypes (e.g. FreeBSD when compiling without
+# -Wall), it does not work. By looking for function definition in
+# libraries, this problem can be worked around.
AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[
AC_SEARCH_LIBS([$1],,
[$2],[$3])
],[$3])])
-dnl
-dnl GIT_STASH_FLAGS(BASEPATH_VAR)
-dnl -----------------------------
-dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running
-dnl tests that may want to take user settings into account.
+#
+# GIT_STASH_FLAGS(BASEPATH_VAR)
+# -----------------------------
+# Allow for easy stashing of LDFLAGS and CPPFLAGS before running
+# tests that may want to take user settings into account.
AC_DEFUN([GIT_STASH_FLAGS],[
if test -n "$1"; then
old_CPPFLAGS="$CPPFLAGS"
fi
])
+## Configure body starts here.
+
+AC_PREREQ(2.59)
+AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
+
+AC_CONFIG_SRCDIR([git.c])
+
+config_file=config.mak.autogen
+config_append=config.mak.append
+config_in=config.mak.in
+
+echo "# ${config_append}. Generated by configure." > "${config_append}"
+
# Directories holding "saner" versions of common or POSIX binaries.
AC_ARG_WITH([sane-tool-path],
[AS_HELP_STRING(
AC_ARG_WITH([lib],
[AS_HELP_STRING([--with-lib=ARG],
[ARG specifies alternative name for lib directory])],
- [if test "$withval" = "no" || test "$withval" = "yes"; then \
- AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
-else \
- lib=$withval; \
- AC_MSG_NOTICE([Setting lib to '$lib']); \
- GIT_CONF_APPEND_LINE(lib=$withval); \
-fi; \
-],[])
+ [if test "$withval" = "no" || test "$withval" = "yes"; then
+ AC_MSG_WARN([You should provide name for --with-lib=ARG])
+ else
+ lib=$withval
+ AC_MSG_NOTICE([Setting lib to '$lib'])
+ GIT_CONF_APPEND_LINE(lib=$withval)
+ fi])
if test -z "$lib"; then
AC_MSG_NOTICE([Setting lib to 'lib' (the default)])
# /foo/bar/include and /foo/bar/lib directories.
AC_ARG_WITH(openssl,
AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
-AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\
-GIT_PARSE_WITH(openssl))
-#
+AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),
+GIT_PARSE_WITH([openssl]))
+
# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
# able to use Perl-compatible regular expressions.
#
AC_ARG_WITH(libpcre,
AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]),
-if test "$withval" = "no"; then \
- USE_LIBPCRE=; \
-elif test "$withval" = "yes"; then \
- USE_LIBPCRE=YesPlease; \
-else
- USE_LIBPCRE=YesPlease; \
- LIBPCREDIR=$withval; \
- AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \
- GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \
-fi \
-)
+ if test "$withval" = "no"; then
+ USE_LIBPCRE=
+ elif test "$withval" = "yes"; then
+ USE_LIBPCRE=YesPlease
+ else
+ USE_LIBPCRE=YesPlease
+ LIBPCREDIR=$withval
+ AC_MSG_NOTICE([Setting LIBPCREDIR to $withval])
+ GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval)
+ fi)
#
# Define NO_CURL if you do not have curl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.])
AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if])
-AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\
+AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),
GIT_PARSE_WITH(tcltk))
#
fi
}
-# __gitcomp_1 requires 2 arguments
__gitcomp_1 ()
{
- local c IFS=' '$'\t'$'\n'
+ local c IFS=$' \t\n'
for c in $1; do
- case "$c$2" in
- --*=*) printf %s$'\n' "$c$2" ;;
- *.) printf %s$'\n' "$c$2" ;;
- *) printf %s$'\n' "$c$2 " ;;
+ c="$c$2"
+ case $c in
+ --*=*|*.) ;;
+ *) c="$c " ;;
esac
+ printf '%s\n' "$c"
done
}
*) pfx="$ref:$pfx" ;;
esac
- local IFS=$'\n'
- COMPREPLY=($(compgen -P "$pfx" \
- -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
+ __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
| sed '/^100... blob /{
s,^.* ,,
s,$, ,
s,$,/,
}
s/^.* //')" \
- -- "$cur_"))
+ "$pfx" "$cur_" ""
;;
*...*)
pfx="${cur_%...*}..."
__gitcomp '--ref'
;;
,*)
- case "${words[cword-1]}" in
+ case "$prev" in
--ref)
__gitcomp_nl "$(__git_refs)"
;;
prune,*)
;;
*)
- case "${words[cword-1]}" in
+ case "$prev" in
-m|-F)
;;
*)
{
local i c=1 command __git_dir
- if [[ -n ${ZSH_VERSION-} ]]; then
- emulate -L bash
- setopt KSH_TYPESET
-
- # workaround zsh's bug that leaves 'words' as a special
- # variable in versions < 4.3.12
- typeset -h words
-
- # workaround zsh's bug that quotes spaces in the COMPREPLY
- # array if IFS doesn't contain spaces.
- typeset -h IFS
- fi
-
- local cur words cword prev
- _get_comp_words_by_ref -n =: cur words cword prev
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;;
- --version|-p|--paginate) ;;
--help) command="help"; break ;;
+ -c) c=$((++c)) ;;
+ -*) ;;
*) command="$i"; break ;;
esac
((c++))
--bare
--version
--exec-path
+ --exec-path=
--html-path
+ --info-path
--work-tree=
--namespace=
+ --no-replace-objects
--help
"
;;
_gitk ()
{
- if [[ -n ${ZSH_VERSION-} ]]; then
- emulate -L bash
- setopt KSH_TYPESET
-
- # workaround zsh's bug that leaves 'words' as a special
- # variable in versions < 4.3.12
- typeset -h words
-
- # workaround zsh's bug that quotes spaces in the COMPREPLY
- # array if IFS doesn't contain spaces.
- typeset -h IFS
- fi
-
- local cur words cword prev
- _get_comp_words_by_ref -n =: cur words cword prev
-
__git_has_doubledash && return
local g="$(__gitdir)"
__git_complete_revlist
}
-complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \
- || complete -o default -o nospace -F _git git
-complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \
- || complete -o default -o nospace -F _gitk gitk
+__git_func_wrap ()
+{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+
+ # workaround zsh's bug that quotes spaces in the COMPREPLY
+ # array if IFS doesn't contain spaces.
+ typeset -h IFS
+ fi
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ $1
+}
+
+# Setup completion for certain functions defined above by setting common
+# variables and workarounds.
+# This is NOT a public function; use at your own risk.
+__git_complete ()
+{
+ local wrapper="__git_wrap${2}"
+ eval "$wrapper () { __git_func_wrap $2 ; }"
+ complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \
+ || complete -o default -o nospace -F $wrapper $1
+}
+
+__git_complete git _git
+__git_complete gitk _gitk
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
- || complete -o default -o nospace -F _git git.exe
+__git_complete git.exe _git
fi
filename = git_path("FETCH_HEAD");
fp = fopen(filename, "a");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error("cannot open %s: %s", filename, strerror(errno));
result = append_fetch_head(fp, argv[2], argv[3],
argv[4], argv[5],
argv[6], !!argv[7][0],
filename = git_path("FETCH_HEAD");
fp = fopen(filename, "a");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error("cannot open %s: %s", filename, strerror(errno));
result = fetch_native_store(fp, argv[2], argv[3], argv[4],
verbose, force);
fclose(fp);
+++ /dev/null
-#!/usr/bin/env python
-#
-# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
-#
-# Author: Simon Hausmann <simon@lst.de>
-# Copyright: 2007 Simon Hausmann <simon@lst.de>
-# 2007 Trolltech ASA
-# License: MIT <http://www.opensource.org/licenses/mit-license.php>
-#
-
-import optparse, sys, os, marshal, subprocess, shelve
-import tempfile, getopt, os.path, time, platform
-import re, shutil
-
-verbose = False
-
-
-def p4_build_cmd(cmd):
- """Build a suitable p4 command line.
-
- This consolidates building and returning a p4 command line into one
- location. It means that hooking into the environment, or other configuration
- can be done more easily.
- """
- real_cmd = ["p4"]
-
- user = gitConfig("git-p4.user")
- if len(user) > 0:
- real_cmd += ["-u",user]
-
- password = gitConfig("git-p4.password")
- if len(password) > 0:
- real_cmd += ["-P", password]
-
- port = gitConfig("git-p4.port")
- if len(port) > 0:
- real_cmd += ["-p", port]
-
- host = gitConfig("git-p4.host")
- if len(host) > 0:
- real_cmd += ["-H", host]
-
- client = gitConfig("git-p4.client")
- if len(client) > 0:
- real_cmd += ["-c", client]
-
-
- if isinstance(cmd,basestring):
- real_cmd = ' '.join(real_cmd) + ' ' + cmd
- else:
- real_cmd += cmd
- return real_cmd
-
-def chdir(dir):
- # P4 uses the PWD environment variable rather than getcwd(). Since we're
- # not using the shell, we have to set it ourselves. This path could
- # be relative, so go there first, then figure out where we ended up.
- os.chdir(dir)
- os.environ['PWD'] = os.getcwd()
-
-def die(msg):
- if verbose:
- raise Exception(msg)
- else:
- sys.stderr.write(msg + "\n")
- sys.exit(1)
-
-def write_pipe(c, stdin):
- if verbose:
- sys.stderr.write('Writing pipe: %s\n' % str(c))
-
- expand = isinstance(c,basestring)
- p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
- pipe = p.stdin
- val = pipe.write(stdin)
- pipe.close()
- if p.wait():
- die('Command failed: %s' % str(c))
-
- return val
-
-def p4_write_pipe(c, stdin):
- real_cmd = p4_build_cmd(c)
- return write_pipe(real_cmd, stdin)
-
-def read_pipe(c, ignore_error=False):
- if verbose:
- sys.stderr.write('Reading pipe: %s\n' % str(c))
-
- expand = isinstance(c,basestring)
- p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
- pipe = p.stdout
- val = pipe.read()
- if p.wait() and not ignore_error:
- die('Command failed: %s' % str(c))
-
- return val
-
-def p4_read_pipe(c, ignore_error=False):
- real_cmd = p4_build_cmd(c)
- return read_pipe(real_cmd, ignore_error)
-
-def read_pipe_lines(c):
- if verbose:
- sys.stderr.write('Reading pipe: %s\n' % str(c))
-
- expand = isinstance(c, basestring)
- p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
- pipe = p.stdout
- val = pipe.readlines()
- if pipe.close() or p.wait():
- die('Command failed: %s' % str(c))
-
- return val
-
-def p4_read_pipe_lines(c):
- """Specifically invoke p4 on the command supplied. """
- real_cmd = p4_build_cmd(c)
- return read_pipe_lines(real_cmd)
-
-def system(cmd):
- expand = isinstance(cmd,basestring)
- if verbose:
- sys.stderr.write("executing %s\n" % str(cmd))
- subprocess.check_call(cmd, shell=expand)
-
-def p4_system(cmd):
- """Specifically invoke p4 as the system command. """
- real_cmd = p4_build_cmd(cmd)
- expand = isinstance(real_cmd, basestring)
- subprocess.check_call(real_cmd, shell=expand)
-
-def p4_integrate(src, dest):
- p4_system(["integrate", "-Dt", src, dest])
-
-def p4_sync(path):
- p4_system(["sync", path])
-
-def p4_add(f):
- p4_system(["add", f])
-
-def p4_delete(f):
- p4_system(["delete", f])
-
-def p4_edit(f):
- p4_system(["edit", f])
-
-def p4_revert(f):
- p4_system(["revert", f])
-
-def p4_reopen(type, file):
- p4_system(["reopen", "-t", type, file])
-
-#
-# Canonicalize the p4 type and return a tuple of the
-# base type, plus any modifiers. See "p4 help filetypes"
-# for a list and explanation.
-#
-def split_p4_type(p4type):
-
- p4_filetypes_historical = {
- "ctempobj": "binary+Sw",
- "ctext": "text+C",
- "cxtext": "text+Cx",
- "ktext": "text+k",
- "kxtext": "text+kx",
- "ltext": "text+F",
- "tempobj": "binary+FSw",
- "ubinary": "binary+F",
- "uresource": "resource+F",
- "uxbinary": "binary+Fx",
- "xbinary": "binary+x",
- "xltext": "text+Fx",
- "xtempobj": "binary+Swx",
- "xtext": "text+x",
- "xunicode": "unicode+x",
- "xutf16": "utf16+x",
- }
- if p4type in p4_filetypes_historical:
- p4type = p4_filetypes_historical[p4type]
- mods = ""
- s = p4type.split("+")
- base = s[0]
- mods = ""
- if len(s) > 1:
- mods = s[1]
- return (base, mods)
-
-#
-# return the raw p4 type of a file (text, text+ko, etc)
-#
-def p4_type(file):
- results = p4CmdList(["fstat", "-T", "headType", file])
- return results[0]['headType']
-
-#
-# Given a type base and modifier, return a regexp matching
-# the keywords that can be expanded in the file
-#
-def p4_keywords_regexp_for_type(base, type_mods):
- if base in ("text", "unicode", "binary"):
- kwords = None
- if "ko" in type_mods:
- kwords = 'Id|Header'
- elif "k" in type_mods:
- kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
- else:
- return None
- pattern = r"""
- \$ # Starts with a dollar, followed by...
- (%s) # one of the keywords, followed by...
- (:[^$]+)? # possibly an old expansion, followed by...
- \$ # another dollar
- """ % kwords
- return pattern
- else:
- return None
-
-#
-# Given a file, return a regexp matching the possible
-# RCS keywords that will be expanded, or None for files
-# with kw expansion turned off.
-#
-def p4_keywords_regexp_for_file(file):
- if not os.path.exists(file):
- return None
- else:
- (type_base, type_mods) = split_p4_type(p4_type(file))
- return p4_keywords_regexp_for_type(type_base, type_mods)
-
-def setP4ExecBit(file, mode):
- # Reopens an already open file and changes the execute bit to match
- # the execute bit setting in the passed in mode.
-
- p4Type = "+x"
-
- if not isModeExec(mode):
- p4Type = getP4OpenedType(file)
- p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
- p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
- if p4Type[-1] == "+":
- p4Type = p4Type[0:-1]
-
- p4_reopen(p4Type, file)
-
-def getP4OpenedType(file):
- # Returns the perforce file type for the given file.
-
- result = p4_read_pipe(["opened", file])
- match = re.match(".*\((.+)\)\r?$", result)
- if match:
- return match.group(1)
- else:
- die("Could not determine file type for %s (result: '%s')" % (file, result))
-
-def diffTreePattern():
- # This is a simple generator for the diff tree regex pattern. This could be
- # a class variable if this and parseDiffTreeEntry were a part of a class.
- pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
- while True:
- yield pattern
-
-def parseDiffTreeEntry(entry):
- """Parses a single diff tree entry into its component elements.
-
- See git-diff-tree(1) manpage for details about the format of the diff
- output. This method returns a dictionary with the following elements:
-
- src_mode - The mode of the source file
- dst_mode - The mode of the destination file
- src_sha1 - The sha1 for the source file
- dst_sha1 - The sha1 fr the destination file
- status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
- status_score - The score for the status (applicable for 'C' and 'R'
- statuses). This is None if there is no score.
- src - The path for the source file.
- dst - The path for the destination file. This is only present for
- copy or renames. If it is not present, this is None.
-
- If the pattern is not matched, None is returned."""
-
- match = diffTreePattern().next().match(entry)
- if match:
- return {
- 'src_mode': match.group(1),
- 'dst_mode': match.group(2),
- 'src_sha1': match.group(3),
- 'dst_sha1': match.group(4),
- 'status': match.group(5),
- 'status_score': match.group(6),
- 'src': match.group(7),
- 'dst': match.group(10)
- }
- return None
-
-def isModeExec(mode):
- # Returns True if the given git mode represents an executable file,
- # otherwise False.
- return mode[-3:] == "755"
-
-def isModeExecChanged(src_mode, dst_mode):
- return isModeExec(src_mode) != isModeExec(dst_mode)
-
-def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
-
- if isinstance(cmd,basestring):
- cmd = "-G " + cmd
- expand = True
- else:
- cmd = ["-G"] + cmd
- expand = False
-
- cmd = p4_build_cmd(cmd)
- if verbose:
- sys.stderr.write("Opening pipe: %s\n" % str(cmd))
-
- # Use a temporary file to avoid deadlocks without
- # subprocess.communicate(), which would put another copy
- # of stdout into memory.
- stdin_file = None
- if stdin is not None:
- stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
- if isinstance(stdin,basestring):
- stdin_file.write(stdin)
- else:
- for i in stdin:
- stdin_file.write(i + '\n')
- stdin_file.flush()
- stdin_file.seek(0)
-
- p4 = subprocess.Popen(cmd,
- shell=expand,
- stdin=stdin_file,
- stdout=subprocess.PIPE)
-
- result = []
- try:
- while True:
- entry = marshal.load(p4.stdout)
- if cb is not None:
- cb(entry)
- else:
- result.append(entry)
- except EOFError:
- pass
- exitCode = p4.wait()
- if exitCode != 0:
- entry = {}
- entry["p4ExitCode"] = exitCode
- result.append(entry)
-
- return result
-
-def p4Cmd(cmd):
- list = p4CmdList(cmd)
- result = {}
- for entry in list:
- result.update(entry)
- return result;
-
-def p4Where(depotPath):
- if not depotPath.endswith("/"):
- depotPath += "/"
- depotPath = depotPath + "..."
- outputList = p4CmdList(["where", depotPath])
- output = None
- for entry in outputList:
- if "depotFile" in entry:
- if entry["depotFile"] == depotPath:
- output = entry
- break
- elif "data" in entry:
- data = entry.get("data")
- space = data.find(" ")
- if data[:space] == depotPath:
- output = entry
- break
- if output == None:
- return ""
- if output["code"] == "error":
- return ""
- clientPath = ""
- if "path" in output:
- clientPath = output.get("path")
- elif "data" in output:
- data = output.get("data")
- lastSpace = data.rfind(" ")
- clientPath = data[lastSpace + 1:]
-
- if clientPath.endswith("..."):
- clientPath = clientPath[:-3]
- return clientPath
-
-def currentGitBranch():
- return read_pipe("git name-rev HEAD").split(" ")[1].strip()
-
-def isValidGitDir(path):
- if (os.path.exists(path + "/HEAD")
- and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
- return True;
- return False
-
-def parseRevision(ref):
- return read_pipe("git rev-parse %s" % ref).strip()
-
-def branchExists(ref):
- rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
- ignore_error=True)
- return len(rev) > 0
-
-def extractLogMessageFromGitCommit(commit):
- logMessage = ""
-
- ## fixme: title is first line of commit, not 1st paragraph.
- foundTitle = False
- for log in read_pipe_lines("git cat-file commit %s" % commit):
- if not foundTitle:
- if len(log) == 1:
- foundTitle = True
- continue
-
- logMessage += log
- return logMessage
-
-def extractSettingsGitLog(log):
- values = {}
- for line in log.split("\n"):
- line = line.strip()
- m = re.search (r"^ *\[git-p4: (.*)\]$", line)
- if not m:
- continue
-
- assignments = m.group(1).split (':')
- for a in assignments:
- vals = a.split ('=')
- key = vals[0].strip()
- val = ('='.join (vals[1:])).strip()
- if val.endswith ('\"') and val.startswith('"'):
- val = val[1:-1]
-
- values[key] = val
-
- paths = values.get("depot-paths")
- if not paths:
- paths = values.get("depot-path")
- if paths:
- values['depot-paths'] = paths.split(',')
- return values
-
-def gitBranchExists(branch):
- proc = subprocess.Popen(["git", "rev-parse", branch],
- stderr=subprocess.PIPE, stdout=subprocess.PIPE);
- return proc.wait() == 0;
-
-_gitConfig = {}
-def gitConfig(key, args = None): # set args to "--bool", for instance
- if not _gitConfig.has_key(key):
- argsFilter = ""
- if args != None:
- argsFilter = "%s " % args
- cmd = "git config %s%s" % (argsFilter, key)
- _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
- return _gitConfig[key]
-
-def gitConfigList(key):
- if not _gitConfig.has_key(key):
- _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
- return _gitConfig[key]
-
-def p4BranchesInGit(branchesAreInRemotes = True):
- branches = {}
-
- cmdline = "git rev-parse --symbolic "
- if branchesAreInRemotes:
- cmdline += " --remotes"
- else:
- cmdline += " --branches"
-
- for line in read_pipe_lines(cmdline):
- line = line.strip()
-
- ## only import to p4/
- if not line.startswith('p4/') or line == "p4/HEAD":
- continue
- branch = line
-
- # strip off p4
- branch = re.sub ("^p4/", "", line)
-
- branches[branch] = parseRevision(line)
- return branches
-
-def findUpstreamBranchPoint(head = "HEAD"):
- branches = p4BranchesInGit()
- # map from depot-path to branch name
- branchByDepotPath = {}
- for branch in branches.keys():
- tip = branches[branch]
- log = extractLogMessageFromGitCommit(tip)
- settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
- paths = ",".join(settings["depot-paths"])
- branchByDepotPath[paths] = "remotes/p4/" + branch
-
- settings = None
- parent = 0
- while parent < 65535:
- commit = head + "~%s" % parent
- log = extractLogMessageFromGitCommit(commit)
- settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
- paths = ",".join(settings["depot-paths"])
- if branchByDepotPath.has_key(paths):
- return [branchByDepotPath[paths], settings]
-
- parent = parent + 1
-
- return ["", settings]
-
-def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
- if not silent:
- print ("Creating/updating branch(es) in %s based on origin branch(es)"
- % localRefPrefix)
-
- originPrefix = "origin/p4/"
-
- for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
- line = line.strip()
- if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
- continue
-
- headName = line[len(originPrefix):]
- remoteHead = localRefPrefix + headName
- originHead = line
-
- original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
- if (not original.has_key('depot-paths')
- or not original.has_key('change')):
- continue
-
- update = False
- if not gitBranchExists(remoteHead):
- if verbose:
- print "creating %s" % remoteHead
- update = True
- else:
- settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
- if settings.has_key('change') > 0:
- if settings['depot-paths'] == original['depot-paths']:
- originP4Change = int(original['change'])
- p4Change = int(settings['change'])
- if originP4Change > p4Change:
- print ("%s (%s) is newer than %s (%s). "
- "Updating p4 branch from origin."
- % (originHead, originP4Change,
- remoteHead, p4Change))
- update = True
- else:
- print ("Ignoring: %s was imported from %s while "
- "%s was imported from %s"
- % (originHead, ','.join(original['depot-paths']),
- remoteHead, ','.join(settings['depot-paths'])))
-
- if update:
- system("git update-ref %s %s" % (remoteHead, originHead))
-
-def originP4BranchesExist():
- return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
-
-def p4ChangesForPaths(depotPaths, changeRange):
- assert depotPaths
- cmd = ['changes']
- for p in depotPaths:
- cmd += ["%s...%s" % (p, changeRange)]
- output = p4_read_pipe_lines(cmd)
-
- changes = {}
- for line in output:
- changeNum = int(line.split(" ")[1])
- changes[changeNum] = True
-
- changelist = changes.keys()
- changelist.sort()
- return changelist
-
-def p4PathStartsWith(path, prefix):
- # This method tries to remedy a potential mixed-case issue:
- #
- # If UserA adds //depot/DirA/file1
- # and UserB adds //depot/dira/file2
- #
- # we may or may not have a problem. If you have core.ignorecase=true,
- # we treat DirA and dira as the same directory
- ignorecase = gitConfig("core.ignorecase", "--bool") == "true"
- if ignorecase:
- return path.lower().startswith(prefix.lower())
- return path.startswith(prefix)
-
-def getClientSpec():
- """Look at the p4 client spec, create a View() object that contains
- all the mappings, and return it."""
-
- specList = p4CmdList("client -o")
- if len(specList) != 1:
- die('Output from "client -o" is %d lines, expecting 1' %
- len(specList))
-
- # dictionary of all client parameters
- entry = specList[0]
-
- # just the keys that start with "View"
- view_keys = [ k for k in entry.keys() if k.startswith("View") ]
-
- # hold this new View
- view = View()
-
- # append the lines, in order, to the view
- for view_num in range(len(view_keys)):
- k = "View%d" % view_num
- if k not in view_keys:
- die("Expected view key %s missing" % k)
- view.append(entry[k])
-
- return view
-
-def getClientRoot():
- """Grab the client directory."""
-
- output = p4CmdList("client -o")
- if len(output) != 1:
- die('Output from "client -o" is %d lines, expecting 1' % len(output))
-
- entry = output[0]
- if "Root" not in entry:
- die('Client has no "Root"')
-
- return entry["Root"]
-
-class Command:
- def __init__(self):
- self.usage = "usage: %prog [options]"
- self.needsGit = True
-
-class P4UserMap:
- def __init__(self):
- self.userMapFromPerforceServer = False
- self.myP4UserId = None
-
- def p4UserId(self):
- if self.myP4UserId:
- return self.myP4UserId
-
- results = p4CmdList("user -o")
- for r in results:
- if r.has_key('User'):
- self.myP4UserId = r['User']
- return r['User']
- die("Could not find your p4 user id")
-
- def p4UserIsMe(self, p4User):
- # return True if the given p4 user is actually me
- me = self.p4UserId()
- if not p4User or p4User != me:
- return False
- else:
- return True
-
- def getUserCacheFilename(self):
- home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
- return home + "/.gitp4-usercache.txt"
-
- def getUserMapFromPerforceServer(self):
- if self.userMapFromPerforceServer:
- return
- self.users = {}
- self.emails = {}
-
- for output in p4CmdList("users"):
- if not output.has_key("User"):
- continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
- self.emails[output["Email"]] = output["User"]
-
-
- s = ''
- for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
-
- open(self.getUserCacheFilename(), "wb").write(s)
- self.userMapFromPerforceServer = True
-
- def loadUserMapFromCache(self):
- self.users = {}
- self.userMapFromPerforceServer = False
- try:
- cache = open(self.getUserCacheFilename(), "rb")
- lines = cache.readlines()
- cache.close()
- for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
- except IOError:
- self.getUserMapFromPerforceServer()
-
-class P4Debug(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true",
- default=False),
- ]
- self.description = "A tool to debug the output of p4 -G."
- self.needsGit = False
- self.verbose = False
-
- def run(self, args):
- j = 0
- for output in p4CmdList(args):
- print 'Element: %d' % j
- j += 1
- print output
- return True
-
-class P4RollBack(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
- optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
- ]
- self.description = "A tool to debug the multi-branch import. Don't use :)"
- self.verbose = False
- self.rollbackLocalBranches = False
-
- def run(self, args):
- if len(args) != 1:
- return False
- maxChange = int(args[0])
-
- if "p4ExitCode" in p4Cmd("changes -m 1"):
- die("Problems executing p4");
-
- if self.rollbackLocalBranches:
- refPrefix = "refs/heads/"
- lines = read_pipe_lines("git rev-parse --symbolic --branches")
- else:
- refPrefix = "refs/remotes/"
- lines = read_pipe_lines("git rev-parse --symbolic --remotes")
-
- for line in lines:
- if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
- line = line.strip()
- ref = refPrefix + line
- log = extractLogMessageFromGitCommit(ref)
- settings = extractSettingsGitLog(log)
-
- depotPaths = settings['depot-paths']
- change = settings['change']
-
- changed = False
-
- if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
- for p in depotPaths]))) == 0:
- print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
- system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
- continue
-
- while change and int(change) > maxChange:
- changed = True
- if self.verbose:
- print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
- system("git update-ref %s \"%s^\"" % (ref, ref))
- log = extractLogMessageFromGitCommit(ref)
- settings = extractSettingsGitLog(log)
-
-
- depotPaths = settings['depot-paths']
- change = settings['change']
-
- if changed:
- print "%s rewound to %s" % (ref, change)
-
- return True
-
-class P4Submit(Command, P4UserMap):
- def __init__(self):
- Command.__init__(self)
- P4UserMap.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
- optparse.make_option("--origin", dest="origin"),
- optparse.make_option("-M", dest="detectRenames", action="store_true"),
- # preserve the user, requires relevant p4 permissions
- optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
- ]
- self.description = "Submit changes from git to the perforce depot."
- self.usage += " [name of git branch to submit into perforce depot]"
- self.interactive = True
- self.origin = ""
- self.detectRenames = False
- self.verbose = False
- self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
- self.isWindows = (platform.system() == "Windows")
-
- def check(self):
- if len(p4CmdList("opened ...")) > 0:
- die("You have files opened with perforce! Close them before starting the sync.")
-
- # replaces everything between 'Description:' and the next P4 submit template field with the
- # commit message
- def prepareLogMessage(self, template, message):
- result = ""
-
- inDescriptionSection = False
-
- for line in template.split("\n"):
- if line.startswith("#"):
- result += line + "\n"
- continue
-
- if inDescriptionSection:
- if line.startswith("Files:") or line.startswith("Jobs:"):
- inDescriptionSection = False
- else:
- continue
- else:
- if line.startswith("Description:"):
- inDescriptionSection = True
- line += "\n"
- for messageLine in message.split("\n"):
- line += "\t" + messageLine + "\n"
-
- result += line + "\n"
-
- return result
-
- def patchRCSKeywords(self, file, pattern):
- # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
- (handle, outFileName) = tempfile.mkstemp(dir='.')
- try:
- outFile = os.fdopen(handle, "w+")
- inFile = open(file, "r")
- regexp = re.compile(pattern, re.VERBOSE)
- for line in inFile.readlines():
- line = regexp.sub(r'$\1$', line)
- outFile.write(line)
- inFile.close()
- outFile.close()
- # Forcibly overwrite the original file
- os.unlink(file)
- shutil.move(outFileName, file)
- except:
- # cleanup our temporary file
- os.unlink(outFileName)
- print "Failed to strip RCS keywords in %s" % file
- raise
-
- print "Patched up RCS keywords in %s" % file
-
- def p4UserForCommit(self,id):
- # Return the tuple (perforce user,git email) for a given git commit id
- self.getUserMapFromPerforceServer()
- gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
- gitEmail = gitEmail.strip()
- if not self.emails.has_key(gitEmail):
- return (None,gitEmail)
- else:
- return (self.emails[gitEmail],gitEmail)
-
- def checkValidP4Users(self,commits):
- # check if any git authors cannot be mapped to p4 users
- for id in commits:
- (user,email) = self.p4UserForCommit(id)
- if not user:
- msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
- if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
- print "%s" % msg
- else:
- die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
-
- def lastP4Changelist(self):
- # Get back the last changelist number submitted in this client spec. This
- # then gets used to patch up the username in the change. If the same
- # client spec is being used by multiple processes then this might go
- # wrong.
- results = p4CmdList("client -o") # find the current client
- client = None
- for r in results:
- if r.has_key('Client'):
- client = r['Client']
- break
- if not client:
- die("could not get client spec")
- results = p4CmdList(["changes", "-c", client, "-m", "1"])
- for r in results:
- if r.has_key('change'):
- return r['change']
- die("Could not get changelist number for last submit - cannot patch up user details")
-
- def modifyChangelistUser(self, changelist, newUser):
- # fixup the user field of a changelist after it has been submitted.
- changes = p4CmdList("change -o %s" % changelist)
- if len(changes) != 1:
- die("Bad output from p4 change modifying %s to user %s" %
- (changelist, newUser))
-
- c = changes[0]
- if c['User'] == newUser: return # nothing to do
- c['User'] = newUser
- input = marshal.dumps(c)
-
- result = p4CmdList("change -f -i", stdin=input)
- for r in result:
- if r.has_key('code'):
- if r['code'] == 'error':
- die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
- if r.has_key('data'):
- print("Updated user field for changelist %s to %s" % (changelist, newUser))
- return
- die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
-
- def canChangeChangelists(self):
- # check to see if we have p4 admin or super-user permissions, either of
- # which are required to modify changelists.
- results = p4CmdList(["protects", self.depotPath])
- for r in results:
- if r.has_key('perm'):
- if r['perm'] == 'admin':
- return 1
- if r['perm'] == 'super':
- return 1
- return 0
-
- def prepareSubmitTemplate(self):
- # remove lines in the Files section that show changes to files outside the depot path we're committing into
- template = ""
- inFilesSection = False
- for line in p4_read_pipe_lines(['change', '-o']):
- if line.endswith("\r\n"):
- line = line[:-2] + "\n"
- if inFilesSection:
- if line.startswith("\t"):
- # path starts and ends with a tab
- path = line[1:]
- lastTab = path.rfind("\t")
- if lastTab != -1:
- path = path[:lastTab]
- if not p4PathStartsWith(path, self.depotPath):
- continue
- else:
- inFilesSection = False
- else:
- if line.startswith("Files:"):
- inFilesSection = True
-
- template += line
-
- return template
-
- def edit_template(self, template_file):
- """Invoke the editor to let the user change the submission
- message. Return true if okay to continue with the submit."""
-
- # if configured to skip the editing part, just submit
- if gitConfig("git-p4.skipSubmitEdit") == "true":
- return True
-
- # look at the modification time, to check later if the user saved
- # the file
- mtime = os.stat(template_file).st_mtime
-
- # invoke the editor
- if os.environ.has_key("P4EDITOR"):
- editor = os.environ.get("P4EDITOR")
- else:
- editor = read_pipe("git var GIT_EDITOR").strip()
- system(editor + " " + template_file)
-
- # If the file was not saved, prompt to see if this patch should
- # be skipped. But skip this verification step if configured so.
- if gitConfig("git-p4.skipSubmitEditCheck") == "true":
- return True
-
- # modification time updated means user saved the file
- if os.stat(template_file).st_mtime > mtime:
- return True
-
- while True:
- response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
- if response == 'y':
- return True
- if response == 'n':
- return False
-
- def applyCommit(self, id):
- print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
-
- (p4User, gitEmail) = self.p4UserForCommit(id)
-
- if not self.detectRenames:
- # If not explicitly set check the config variable
- self.detectRenames = gitConfig("git-p4.detectRenames")
-
- if self.detectRenames.lower() == "false" or self.detectRenames == "":
- diffOpts = ""
- elif self.detectRenames.lower() == "true":
- diffOpts = "-M"
- else:
- diffOpts = "-M%s" % self.detectRenames
-
- detectCopies = gitConfig("git-p4.detectCopies")
- if detectCopies.lower() == "true":
- diffOpts += " -C"
- elif detectCopies != "" and detectCopies.lower() != "false":
- diffOpts += " -C%s" % detectCopies
-
- if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
- diffOpts += " --find-copies-harder"
-
- diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
- filesToAdd = set()
- filesToDelete = set()
- editedFiles = set()
- filesToChangeExecBit = {}
-
- for line in diff:
- diff = parseDiffTreeEntry(line)
- modifier = diff['status']
- path = diff['src']
- if modifier == "M":
- p4_edit(path)
- if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
- filesToChangeExecBit[path] = diff['dst_mode']
- editedFiles.add(path)
- elif modifier == "A":
- filesToAdd.add(path)
- filesToChangeExecBit[path] = diff['dst_mode']
- if path in filesToDelete:
- filesToDelete.remove(path)
- elif modifier == "D":
- filesToDelete.add(path)
- if path in filesToAdd:
- filesToAdd.remove(path)
- elif modifier == "C":
- src, dest = diff['src'], diff['dst']
- p4_integrate(src, dest)
- if diff['src_sha1'] != diff['dst_sha1']:
- p4_edit(dest)
- if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
- p4_edit(dest)
- filesToChangeExecBit[dest] = diff['dst_mode']
- os.unlink(dest)
- editedFiles.add(dest)
- elif modifier == "R":
- src, dest = diff['src'], diff['dst']
- p4_integrate(src, dest)
- if diff['src_sha1'] != diff['dst_sha1']:
- p4_edit(dest)
- if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
- p4_edit(dest)
- filesToChangeExecBit[dest] = diff['dst_mode']
- os.unlink(dest)
- editedFiles.add(dest)
- filesToDelete.add(src)
- else:
- die("unknown modifier %s for %s" % (modifier, path))
-
- diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
- patchcmd = diffcmd + " | git apply "
- tryPatchCmd = patchcmd + "--check -"
- applyPatchCmd = patchcmd + "--check --apply -"
- patch_succeeded = True
-
- if os.system(tryPatchCmd) != 0:
- fixed_rcs_keywords = False
- patch_succeeded = False
- print "Unfortunately applying the change failed!"
-
- # Patch failed, maybe it's just RCS keyword woes. Look through
- # the patch to see if that's possible.
- if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
- file = None
- pattern = None
- kwfiles = {}
- for file in editedFiles | filesToDelete:
- # did this file's delta contain RCS keywords?
- pattern = p4_keywords_regexp_for_file(file)
-
- if pattern:
- # this file is a possibility...look for RCS keywords.
- regexp = re.compile(pattern, re.VERBOSE)
- for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
- if regexp.search(line):
- if verbose:
- print "got keyword match on %s in %s in %s" % (pattern, line, file)
- kwfiles[file] = pattern
- break
-
- for file in kwfiles:
- if verbose:
- print "zapping %s with %s" % (line,pattern)
- self.patchRCSKeywords(file, kwfiles[file])
- fixed_rcs_keywords = True
-
- if fixed_rcs_keywords:
- print "Retrying the patch with RCS keywords cleaned up"
- if os.system(tryPatchCmd) == 0:
- patch_succeeded = True
-
- if not patch_succeeded:
- print "What do you want to do?"
- response = "x"
- while response != "s" and response != "a" and response != "w":
- response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
- "and with .rej files / [w]rite the patch to a file (patch.txt) ")
- if response == "s":
- print "Skipping! Good luck with the next patches..."
- for f in editedFiles:
- p4_revert(f)
- for f in filesToAdd:
- os.remove(f)
- return
- elif response == "a":
- os.system(applyPatchCmd)
- if len(filesToAdd) > 0:
- print "You may also want to call p4 add on the following files:"
- print " ".join(filesToAdd)
- if len(filesToDelete):
- print "The following files should be scheduled for deletion with p4 delete:"
- print " ".join(filesToDelete)
- die("Please resolve and submit the conflict manually and "
- + "continue afterwards with git-p4 submit --continue")
- elif response == "w":
- system(diffcmd + " > patch.txt")
- print "Patch saved to patch.txt in %s !" % self.clientPath
- die("Please resolve and submit the conflict manually and "
- "continue afterwards with git-p4 submit --continue")
-
- system(applyPatchCmd)
-
- for f in filesToAdd:
- p4_add(f)
- for f in filesToDelete:
- p4_revert(f)
- p4_delete(f)
-
- # Set/clear executable bits
- for f in filesToChangeExecBit.keys():
- mode = filesToChangeExecBit[f]
- setP4ExecBit(f, mode)
-
- logMessage = extractLogMessageFromGitCommit(id)
- logMessage = logMessage.strip()
-
- template = self.prepareSubmitTemplate()
-
- if self.interactive:
- submitTemplate = self.prepareLogMessage(template, logMessage)
-
- if self.preserveUser:
- submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
-
- if os.environ.has_key("P4DIFF"):
- del(os.environ["P4DIFF"])
- diff = ""
- for editedFile in editedFiles:
- diff += p4_read_pipe(['diff', '-du', editedFile])
-
- newdiff = ""
- for newFile in filesToAdd:
- newdiff += "==== new file ====\n"
- newdiff += "--- /dev/null\n"
- newdiff += "+++ %s\n" % newFile
- f = open(newFile, "r")
- for line in f.readlines():
- newdiff += "+" + line
- f.close()
-
- if self.checkAuthorship and not self.p4UserIsMe(p4User):
- submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
- submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
- submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
-
- separatorLine = "######## everything below this line is just the diff #######\n"
-
- (handle, fileName) = tempfile.mkstemp()
- tmpFile = os.fdopen(handle, "w+")
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\n", "\r\n")
- separatorLine = separatorLine.replace("\n", "\r\n")
- newdiff = newdiff.replace("\n", "\r\n")
- tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
- tmpFile.close()
-
- if self.edit_template(fileName):
- # read the edited message and submit
- tmpFile = open(fileName, "rb")
- message = tmpFile.read()
- tmpFile.close()
- submitTemplate = message[:message.index(separatorLine)]
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\r\n", "\n")
- p4_write_pipe(['submit', '-i'], submitTemplate)
-
- if self.preserveUser:
- if p4User:
- # Get last changelist number. Cannot easily get it from
- # the submit command output as the output is
- # unmarshalled.
- changelist = self.lastP4Changelist()
- self.modifyChangelistUser(changelist, p4User)
- else:
- # skip this patch
- print "Submission cancelled, undoing p4 changes."
- for f in editedFiles:
- p4_revert(f)
- for f in filesToAdd:
- p4_revert(f)
- os.remove(f)
-
- os.remove(fileName)
- else:
- fileName = "submit.txt"
- file = open(fileName, "w+")
- file.write(self.prepareLogMessage(template, logMessage))
- file.close()
- print ("Perforce submit template written as %s. "
- + "Please review/edit and then use p4 submit -i < %s to submit directly!"
- % (fileName, fileName))
-
- def run(self, args):
- if len(args) == 0:
- self.master = currentGitBranch()
- if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
- die("Detecting current git branch failed!")
- elif len(args) == 1:
- self.master = args[0]
- if not branchExists(self.master):
- die("Branch %s does not exist" % self.master)
- else:
- return False
-
- allowSubmit = gitConfig("git-p4.allowSubmit")
- if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
- die("%s is not in git-p4.allowSubmit" % self.master)
-
- [upstream, settings] = findUpstreamBranchPoint()
- self.depotPath = settings['depot-paths'][0]
- if len(self.origin) == 0:
- self.origin = upstream
-
- if self.preserveUser:
- if not self.canChangeChangelists():
- die("Cannot preserve user names without p4 super-user or admin permissions")
-
- if self.verbose:
- print "Origin branch is " + self.origin
-
- if len(self.depotPath) == 0:
- print "Internal error: cannot locate perforce depot path from existing branches"
- sys.exit(128)
-
- self.useClientSpec = False
- if gitConfig("git-p4.useclientspec", "--bool") == "true":
- self.useClientSpec = True
- if self.useClientSpec:
- self.clientSpecDirs = getClientSpec()
-
- if self.useClientSpec:
- # all files are relative to the client spec
- self.clientPath = getClientRoot()
- else:
- self.clientPath = p4Where(self.depotPath)
-
- if self.clientPath == "":
- die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
-
- print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
- self.oldWorkingDirectory = os.getcwd()
-
- # ensure the clientPath exists
- if not os.path.exists(self.clientPath):
- os.makedirs(self.clientPath)
-
- chdir(self.clientPath)
- print "Synchronizing p4 checkout..."
- p4_sync("...")
- self.check()
-
- commits = []
- for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
- commits.append(line.strip())
- commits.reverse()
-
- if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
- self.checkAuthorship = False
- else:
- self.checkAuthorship = True
-
- if self.preserveUser:
- self.checkValidP4Users(commits)
-
- while len(commits) > 0:
- commit = commits[0]
- commits = commits[1:]
- self.applyCommit(commit)
- if not self.interactive:
- break
-
- if len(commits) == 0:
- print "All changes applied!"
- chdir(self.oldWorkingDirectory)
-
- sync = P4Sync()
- sync.run([])
-
- rebase = P4Rebase()
- rebase.rebase()
-
- return True
-
-class View(object):
- """Represent a p4 view ("p4 help views"), and map files in a
- repo according to the view."""
-
- class Path(object):
- """A depot or client path, possibly containing wildcards.
- The only one supported is ... at the end, currently.
- Initialize with the full path, with //depot or //client."""
-
- def __init__(self, path, is_depot):
- self.path = path
- self.is_depot = is_depot
- self.find_wildcards()
- # remember the prefix bit, useful for relative mappings
- m = re.match("(//[^/]+/)", self.path)
- if not m:
- die("Path %s does not start with //prefix/" % self.path)
- prefix = m.group(1)
- if not self.is_depot:
- # strip //client/ on client paths
- self.path = self.path[len(prefix):]
-
- def find_wildcards(self):
- """Make sure wildcards are valid, and set up internal
- variables."""
-
- self.ends_triple_dot = False
- # There are three wildcards allowed in p4 views
- # (see "p4 help views"). This code knows how to
- # handle "..." (only at the end), but cannot deal with
- # "%%n" or "*". Only check the depot_side, as p4 should
- # validate that the client_side matches too.
- if re.search(r'%%[1-9]', self.path):
- die("Can't handle %%n wildcards in view: %s" % self.path)
- if self.path.find("*") >= 0:
- die("Can't handle * wildcards in view: %s" % self.path)
- triple_dot_index = self.path.find("...")
- if triple_dot_index >= 0:
- if triple_dot_index != len(self.path) - 3:
- die("Can handle only single ... wildcard, at end: %s" %
- self.path)
- self.ends_triple_dot = True
-
- def ensure_compatible(self, other_path):
- """Make sure the wildcards agree."""
- if self.ends_triple_dot != other_path.ends_triple_dot:
- die("Both paths must end with ... if either does;\n" +
- "paths: %s %s" % (self.path, other_path.path))
-
- def match_wildcards(self, test_path):
- """See if this test_path matches us, and fill in the value
- of the wildcards if so. Returns a tuple of
- (True|False, wildcards[]). For now, only the ... at end
- is supported, so at most one wildcard."""
- if self.ends_triple_dot:
- dotless = self.path[:-3]
- if test_path.startswith(dotless):
- wildcard = test_path[len(dotless):]
- return (True, [ wildcard ])
- else:
- if test_path == self.path:
- return (True, [])
- return (False, [])
-
- def match(self, test_path):
- """Just return if it matches; don't bother with the wildcards."""
- b, _ = self.match_wildcards(test_path)
- return b
-
- def fill_in_wildcards(self, wildcards):
- """Return the relative path, with the wildcards filled in
- if there are any."""
- if self.ends_triple_dot:
- return self.path[:-3] + wildcards[0]
- else:
- return self.path
-
- class Mapping(object):
- def __init__(self, depot_side, client_side, overlay, exclude):
- # depot_side is without the trailing /... if it had one
- self.depot_side = View.Path(depot_side, is_depot=True)
- self.client_side = View.Path(client_side, is_depot=False)
- self.overlay = overlay # started with "+"
- self.exclude = exclude # started with "-"
- assert not (self.overlay and self.exclude)
- self.depot_side.ensure_compatible(self.client_side)
-
- def __str__(self):
- c = " "
- if self.overlay:
- c = "+"
- if self.exclude:
- c = "-"
- return "View.Mapping: %s%s -> %s" % \
- (c, self.depot_side.path, self.client_side.path)
-
- def map_depot_to_client(self, depot_path):
- """Calculate the client path if using this mapping on the
- given depot path; does not consider the effect of other
- mappings in a view. Even excluded mappings are returned."""
- matches, wildcards = self.depot_side.match_wildcards(depot_path)
- if not matches:
- return ""
- client_path = self.client_side.fill_in_wildcards(wildcards)
- return client_path
-
- #
- # View methods
- #
- def __init__(self):
- self.mappings = []
-
- def append(self, view_line):
- """Parse a view line, splitting it into depot and client
- sides. Append to self.mappings, preserving order."""
-
- # Split the view line into exactly two words. P4 enforces
- # structure on these lines that simplifies this quite a bit.
- #
- # Either or both words may be double-quoted.
- # Single quotes do not matter.
- # Double-quote marks cannot occur inside the words.
- # A + or - prefix is also inside the quotes.
- # There are no quotes unless they contain a space.
- # The line is already white-space stripped.
- # The two words are separated by a single space.
- #
- if view_line[0] == '"':
- # First word is double quoted. Find its end.
- close_quote_index = view_line.find('"', 1)
- if close_quote_index <= 0:
- die("No first-word closing quote found: %s" % view_line)
- depot_side = view_line[1:close_quote_index]
- # skip closing quote and space
- rhs_index = close_quote_index + 1 + 1
- else:
- space_index = view_line.find(" ")
- if space_index <= 0:
- die("No word-splitting space found: %s" % view_line)
- depot_side = view_line[0:space_index]
- rhs_index = space_index + 1
-
- if view_line[rhs_index] == '"':
- # Second word is double quoted. Make sure there is a
- # double quote at the end too.
- if not view_line.endswith('"'):
- die("View line with rhs quote should end with one: %s" %
- view_line)
- # skip the quotes
- client_side = view_line[rhs_index+1:-1]
- else:
- client_side = view_line[rhs_index:]
-
- # prefix + means overlay on previous mapping
- overlay = False
- if depot_side.startswith("+"):
- overlay = True
- depot_side = depot_side[1:]
-
- # prefix - means exclude this path
- exclude = False
- if depot_side.startswith("-"):
- exclude = True
- depot_side = depot_side[1:]
-
- m = View.Mapping(depot_side, client_side, overlay, exclude)
- self.mappings.append(m)
-
- def map_in_client(self, depot_path):
- """Return the relative location in the client where this
- depot file should live. Returns "" if the file should
- not be mapped in the client."""
-
- paths_filled = []
- client_path = ""
-
- # look at later entries first
- for m in self.mappings[::-1]:
-
- # see where will this path end up in the client
- p = m.map_depot_to_client(depot_path)
-
- if p == "":
- # Depot path does not belong in client. Must remember
- # this, as previous items should not cause files to
- # exist in this path either. Remember that the list is
- # being walked from the end, which has higher precedence.
- # Overlap mappings do not exclude previous mappings.
- if not m.overlay:
- paths_filled.append(m.client_side)
-
- else:
- # This mapping matched; no need to search any further.
- # But, the mapping could be rejected if the client path
- # has already been claimed by an earlier mapping (i.e.
- # one later in the list, which we are walking backwards).
- already_mapped_in_client = False
- for f in paths_filled:
- # this is View.Path.match
- if f.match(p):
- already_mapped_in_client = True
- break
- if not already_mapped_in_client:
- # Include this file, unless it is from a line that
- # explicitly said to exclude it.
- if not m.exclude:
- client_path = p
-
- # a match, even if rejected, always stops the search
- break
-
- return client_path
-
-class P4Sync(Command, P4UserMap):
- delete_actions = ( "delete", "move/delete", "purge" )
-
- def __init__(self):
- Command.__init__(self)
- P4UserMap.__init__(self)
- self.options = [
- optparse.make_option("--branch", dest="branch"),
- optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
- optparse.make_option("--changesfile", dest="changesFile"),
- optparse.make_option("--silent", dest="silent", action="store_true"),
- optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
- optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
- help="Import into refs/heads/ , not refs/remotes"),
- optparse.make_option("--max-changes", dest="maxChanges"),
- optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
- help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
- optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
- help="Only sync files that are included in the Perforce Client Spec")
- ]
- self.description = """Imports from Perforce into a git repository.\n
- example:
- //depot/my/project/ -- to import the current head
- //depot/my/project/@all -- to import everything
- //depot/my/project/@1,6 -- to import only from revision 1 to 6
-
- (a ... is not needed in the path p4 specification, it's added implicitly)"""
-
- self.usage += " //depot/path[@revRange]"
- self.silent = False
- self.createdBranches = set()
- self.committedChanges = set()
- self.branch = ""
- self.detectBranches = False
- self.detectLabels = False
- self.changesFile = ""
- self.syncWithOrigin = True
- self.verbose = False
- self.importIntoRemotes = True
- self.maxChanges = ""
- self.isWindows = (platform.system() == "Windows")
- self.keepRepoPath = False
- self.depotPaths = None
- self.p4BranchesInGit = []
- self.cloneExclude = []
- self.useClientSpec = False
- self.useClientSpec_from_options = False
- self.clientSpecDirs = None
- self.tempBranches = []
- self.tempBranchLocation = "git-p4-tmp"
-
- if gitConfig("git-p4.syncFromOrigin") == "false":
- self.syncWithOrigin = False
-
- #
- # P4 wildcards are not allowed in filenames. P4 complains
- # if you simply add them, but you can force it with "-f", in
- # which case it translates them into %xx encoding internally.
- # Search for and fix just these four characters. Do % last so
- # that fixing it does not inadvertently create new %-escapes.
- #
- def wildcard_decode(self, path):
- # Cannot have * in a filename in windows; untested as to
- # what p4 would do in such a case.
- if not self.isWindows:
- path = path.replace("%2A", "*")
- path = path.replace("%23", "#") \
- .replace("%40", "@") \
- .replace("%25", "%")
- return path
-
- # Force a checkpoint in fast-import and wait for it to finish
- def checkpoint(self):
- self.gitStream.write("checkpoint\n\n")
- self.gitStream.write("progress checkpoint\n\n")
- out = self.gitOutput.readline()
- if self.verbose:
- print "checkpoint finished: " + out
-
- def extractFilesFromCommit(self, commit):
- self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
- for path in self.cloneExclude]
- files = []
- fnum = 0
- while commit.has_key("depotFile%s" % fnum):
- path = commit["depotFile%s" % fnum]
-
- if [p for p in self.cloneExclude
- if p4PathStartsWith(path, p)]:
- found = False
- else:
- found = [p for p in self.depotPaths
- if p4PathStartsWith(path, p)]
- if not found:
- fnum = fnum + 1
- continue
-
- file = {}
- file["path"] = path
- file["rev"] = commit["rev%s" % fnum]
- file["action"] = commit["action%s" % fnum]
- file["type"] = commit["type%s" % fnum]
- files.append(file)
- fnum = fnum + 1
- return files
-
- def stripRepoPath(self, path, prefixes):
- if self.useClientSpec:
- return self.clientSpecDirs.map_in_client(path)
-
- if self.keepRepoPath:
- prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
-
- for p in prefixes:
- if p4PathStartsWith(path, p):
- path = path[len(p):]
-
- return path
-
- def splitFilesIntoBranches(self, commit):
- branches = {}
- fnum = 0
- while commit.has_key("depotFile%s" % fnum):
- path = commit["depotFile%s" % fnum]
- found = [p for p in self.depotPaths
- if p4PathStartsWith(path, p)]
- if not found:
- fnum = fnum + 1
- continue
-
- file = {}
- file["path"] = path
- file["rev"] = commit["rev%s" % fnum]
- file["action"] = commit["action%s" % fnum]
- file["type"] = commit["type%s" % fnum]
- fnum = fnum + 1
-
- relPath = self.stripRepoPath(path, self.depotPaths)
-
- for branch in self.knownBranches.keys():
-
- # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
- if relPath.startswith(branch + "/"):
- if branch not in branches:
- branches[branch] = []
- branches[branch].append(file)
- break
-
- return branches
-
- # output one file from the P4 stream
- # - helper for streamP4Files
-
- def streamOneP4File(self, file, contents):
- relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
- relPath = self.wildcard_decode(relPath)
- if verbose:
- sys.stderr.write("%s\n" % relPath)
-
- (type_base, type_mods) = split_p4_type(file["type"])
-
- git_mode = "100644"
- if "x" in type_mods:
- git_mode = "100755"
- if type_base == "symlink":
- git_mode = "120000"
- # p4 print on a symlink contains "target\n"; remove the newline
- data = ''.join(contents)
- contents = [data[:-1]]
-
- if type_base == "utf16":
- # p4 delivers different text in the python output to -G
- # than it does when using "print -o", or normal p4 client
- # operations. utf16 is converted to ascii or utf8, perhaps.
- # But ascii text saved as -t utf16 is completely mangled.
- # Invoke print -o to get the real contents.
- text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
- contents = [ text ]
-
- if type_base == "apple":
- # Apple filetype files will be streamed as a concatenation of
- # its appledouble header and the contents. This is useless
- # on both macs and non-macs. If using "print -q -o xx", it
- # will create "xx" with the data, and "%xx" with the header.
- # This is also not very useful.
- #
- # Ideally, someday, this script can learn how to generate
- # appledouble files directly and import those to git, but
- # non-mac machines can never find a use for apple filetype.
- print "\nIgnoring apple filetype file %s" % file['depotFile']
- return
-
- # Perhaps windows wants unicode, utf16 newlines translated too;
- # but this is not doing it.
- if self.isWindows and type_base == "text":
- mangled = []
- for data in contents:
- data = data.replace("\r\n", "\n")
- mangled.append(data)
- contents = mangled
-
- # Note that we do not try to de-mangle keywords on utf16 files,
- # even though in theory somebody may want that.
- pattern = p4_keywords_regexp_for_type(type_base, type_mods)
- if pattern:
- regexp = re.compile(pattern, re.VERBOSE)
- text = ''.join(contents)
- text = regexp.sub(r'$\1$', text)
- contents = [ text ]
-
- self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
-
- # total length...
- length = 0
- for d in contents:
- length = length + len(d)
-
- self.gitStream.write("data %d\n" % length)
- for d in contents:
- self.gitStream.write(d)
- self.gitStream.write("\n")
-
- def streamOneP4Deletion(self, file):
- relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
- if verbose:
- sys.stderr.write("delete %s\n" % relPath)
- self.gitStream.write("D %s\n" % relPath)
-
- # handle another chunk of streaming data
- def streamP4FilesCb(self, marshalled):
-
- if marshalled.has_key('depotFile') and self.stream_have_file_info:
- # start of a new file - output the old one first
- self.streamOneP4File(self.stream_file, self.stream_contents)
- self.stream_file = {}
- self.stream_contents = []
- self.stream_have_file_info = False
-
- # pick up the new file information... for the
- # 'data' field we need to append to our array
- for k in marshalled.keys():
- if k == 'data':
- self.stream_contents.append(marshalled['data'])
- else:
- self.stream_file[k] = marshalled[k]
-
- self.stream_have_file_info = True
-
- # Stream directly from "p4 files" into "git fast-import"
- def streamP4Files(self, files):
- filesForCommit = []
- filesToRead = []
- filesToDelete = []
-
- for f in files:
- # if using a client spec, only add the files that have
- # a path in the client
- if self.clientSpecDirs:
- if self.clientSpecDirs.map_in_client(f['path']) == "":
- continue
-
- filesForCommit.append(f)
- if f['action'] in self.delete_actions:
- filesToDelete.append(f)
- else:
- filesToRead.append(f)
-
- # deleted files...
- for f in filesToDelete:
- self.streamOneP4Deletion(f)
-
- if len(filesToRead) > 0:
- self.stream_file = {}
- self.stream_contents = []
- self.stream_have_file_info = False
-
- # curry self argument
- def streamP4FilesCbSelf(entry):
- self.streamP4FilesCb(entry)
-
- fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
-
- p4CmdList(["-x", "-", "print"],
- stdin=fileArgs,
- cb=streamP4FilesCbSelf)
-
- # do the last chunk
- if self.stream_file.has_key('depotFile'):
- self.streamOneP4File(self.stream_file, self.stream_contents)
-
- def make_email(self, userid):
- if userid in self.users:
- return self.users[userid]
- else:
- return "%s <a@b>" % userid
-
- def commit(self, details, files, branch, branchPrefixes, parent = ""):
- epoch = details["time"]
- author = details["user"]
- self.branchPrefixes = branchPrefixes
-
- if self.verbose:
- print "commit into %s" % branch
-
- # start with reading files; if that fails, we should not
- # create a commit.
- new_files = []
- for f in files:
- if [p for p in branchPrefixes if p4PathStartsWith(f['path'], p)]:
- new_files.append (f)
- else:
- sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
-
- self.gitStream.write("commit %s\n" % branch)
-# gitStream.write("mark :%s\n" % details["change"])
- self.committedChanges.add(int(details["change"]))
- committer = ""
- if author not in self.users:
- self.getUserMapFromPerforceServer()
- committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
-
- self.gitStream.write("committer %s\n" % committer)
-
- self.gitStream.write("data <<EOT\n")
- self.gitStream.write(details["desc"])
- self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
- % (','.join (branchPrefixes), details["change"]))
- if len(details['options']) > 0:
- self.gitStream.write(": options = %s" % details['options'])
- self.gitStream.write("]\nEOT\n\n")
-
- if len(parent) > 0:
- if self.verbose:
- print "parent %s" % parent
- self.gitStream.write("from %s\n" % parent)
-
- self.streamP4Files(new_files)
- self.gitStream.write("\n")
-
- change = int(details["change"])
-
- if self.labels.has_key(change):
- label = self.labels[change]
- labelDetails = label[0]
- labelRevisions = label[1]
- if self.verbose:
- print "Change %s is labelled %s" % (change, labelDetails)
-
- files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
- for p in branchPrefixes])
-
- if len(files) == len(labelRevisions):
-
- cleanedFiles = {}
- for info in files:
- if info["action"] in self.delete_actions:
- continue
- cleanedFiles[info["depotFile"]] = info["rev"]
-
- if cleanedFiles == labelRevisions:
- self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
- self.gitStream.write("from %s\n" % branch)
-
- owner = labelDetails["Owner"]
-
- # Try to use the owner of the p4 label, or failing that,
- # the current p4 user id.
- if owner:
- email = self.make_email(owner)
- else:
- email = self.make_email(self.p4UserId())
- tagger = "%s %s %s" % (email, epoch, self.tz)
-
- self.gitStream.write("tagger %s\n" % tagger)
-
- description = labelDetails["Description"]
- self.gitStream.write("data %d\n" % len(description))
- self.gitStream.write(description)
- self.gitStream.write("\n")
-
- else:
- if not self.silent:
- print ("Tag %s does not match with change %s: files do not match."
- % (labelDetails["label"], change))
-
- else:
- if not self.silent:
- print ("Tag %s does not match with change %s: file count is different."
- % (labelDetails["label"], change))
-
- def getLabels(self):
- self.labels = {}
-
- l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
- if len(l) > 0 and not self.silent:
- print "Finding files belonging to labels in %s" % `self.depotPaths`
-
- for output in l:
- label = output["label"]
- revisions = {}
- newestChange = 0
- if self.verbose:
- print "Querying files for label %s" % label
- for file in p4CmdList(["files"] +
- ["%s...@%s" % (p, label)
- for p in self.depotPaths]):
- revisions[file["depotFile"]] = file["rev"]
- change = int(file["change"])
- if change > newestChange:
- newestChange = change
-
- self.labels[newestChange] = [output, revisions]
-
- if self.verbose:
- print "Label changes: %s" % self.labels.keys()
-
- def guessProjectName(self):
- for p in self.depotPaths:
- if p.endswith("/"):
- p = p[:-1]
- p = p[p.strip().rfind("/") + 1:]
- if not p.endswith("/"):
- p += "/"
- return p
-
- def getBranchMapping(self):
- lostAndFoundBranches = set()
-
- user = gitConfig("git-p4.branchUser")
- if len(user) > 0:
- command = "branches -u %s" % user
- else:
- command = "branches"
-
- for info in p4CmdList(command):
- details = p4Cmd(["branch", "-o", info["branch"]])
- viewIdx = 0
- while details.has_key("View%s" % viewIdx):
- paths = details["View%s" % viewIdx].split(" ")
- viewIdx = viewIdx + 1
- # require standard //depot/foo/... //depot/bar/... mapping
- if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
- continue
- source = paths[0]
- destination = paths[1]
- ## HACK
- if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
- source = source[len(self.depotPaths[0]):-4]
- destination = destination[len(self.depotPaths[0]):-4]
-
- if destination in self.knownBranches:
- if not self.silent:
- print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
- print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
- continue
-
- self.knownBranches[destination] = source
-
- lostAndFoundBranches.discard(destination)
-
- if source not in self.knownBranches:
- lostAndFoundBranches.add(source)
-
- # Perforce does not strictly require branches to be defined, so we also
- # check git config for a branch list.
- #
- # Example of branch definition in git config file:
- # [git-p4]
- # branchList=main:branchA
- # branchList=main:branchB
- # branchList=branchA:branchC
- configBranches = gitConfigList("git-p4.branchList")
- for branch in configBranches:
- if branch:
- (source, destination) = branch.split(":")
- self.knownBranches[destination] = source
-
- lostAndFoundBranches.discard(destination)
-
- if source not in self.knownBranches:
- lostAndFoundBranches.add(source)
-
-
- for branch in lostAndFoundBranches:
- self.knownBranches[branch] = branch
-
- def getBranchMappingFromGitBranches(self):
- branches = p4BranchesInGit(self.importIntoRemotes)
- for branch in branches.keys():
- if branch == "master":
- branch = "main"
- else:
- branch = branch[len(self.projectName):]
- self.knownBranches[branch] = branch
-
- def listExistingP4GitBranches(self):
- # branches holds mapping from name to commit
- branches = p4BranchesInGit(self.importIntoRemotes)
- self.p4BranchesInGit = branches.keys()
- for branch in branches.keys():
- self.initialParents[self.refPrefix + branch] = branches[branch]
-
- def updateOptionDict(self, d):
- option_keys = {}
- if self.keepRepoPath:
- option_keys['keepRepoPath'] = 1
-
- d["options"] = ' '.join(sorted(option_keys.keys()))
-
- def readOptions(self, d):
- self.keepRepoPath = (d.has_key('options')
- and ('keepRepoPath' in d['options']))
-
- def gitRefForBranch(self, branch):
- if branch == "main":
- return self.refPrefix + "master"
-
- if len(branch) <= 0:
- return branch
-
- return self.refPrefix + self.projectName + branch
-
- def gitCommitByP4Change(self, ref, change):
- if self.verbose:
- print "looking in ref " + ref + " for change %s using bisect..." % change
-
- earliestCommit = ""
- latestCommit = parseRevision(ref)
-
- while True:
- if self.verbose:
- print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
- next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
- if len(next) == 0:
- if self.verbose:
- print "argh"
- return ""
- log = extractLogMessageFromGitCommit(next)
- settings = extractSettingsGitLog(log)
- currentChange = int(settings['change'])
- if self.verbose:
- print "current change %s" % currentChange
-
- if currentChange == change:
- if self.verbose:
- print "found %s" % next
- return next
-
- if currentChange < change:
- earliestCommit = "^%s" % next
- else:
- latestCommit = "%s" % next
-
- return ""
-
- def importNewBranch(self, branch, maxChange):
- # make fast-import flush all changes to disk and update the refs using the checkpoint
- # command so that we can try to find the branch parent in the git history
- self.gitStream.write("checkpoint\n\n");
- self.gitStream.flush();
- branchPrefix = self.depotPaths[0] + branch + "/"
- range = "@1,%s" % maxChange
- #print "prefix" + branchPrefix
- changes = p4ChangesForPaths([branchPrefix], range)
- if len(changes) <= 0:
- return False
- firstChange = changes[0]
- #print "first change in branch: %s" % firstChange
- sourceBranch = self.knownBranches[branch]
- sourceDepotPath = self.depotPaths[0] + sourceBranch
- sourceRef = self.gitRefForBranch(sourceBranch)
- #print "source " + sourceBranch
-
- branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
- #print "branch parent: %s" % branchParentChange
- gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
- if len(gitParent) > 0:
- self.initialParents[self.gitRefForBranch(branch)] = gitParent
- #print "parent git commit: %s" % gitParent
-
- self.importChanges(changes)
- return True
-
- def searchParent(self, parent, branch, target):
- parentFound = False
- for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
- blob = blob.strip()
- if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
- parentFound = True
- if self.verbose:
- print "Found parent of %s in commit %s" % (branch, blob)
- break
- if parentFound:
- return blob
- else:
- return None
-
- def importChanges(self, changes):
- cnt = 1
- for change in changes:
- description = p4Cmd(["describe", str(change)])
- self.updateOptionDict(description)
-
- if not self.silent:
- sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
- sys.stdout.flush()
- cnt = cnt + 1
-
- try:
- if self.detectBranches:
- branches = self.splitFilesIntoBranches(description)
- for branch in branches.keys():
- ## HACK --hwn
- branchPrefix = self.depotPaths[0] + branch + "/"
-
- parent = ""
-
- filesForCommit = branches[branch]
-
- if self.verbose:
- print "branch is %s" % branch
-
- self.updatedBranches.add(branch)
-
- if branch not in self.createdBranches:
- self.createdBranches.add(branch)
- parent = self.knownBranches[branch]
- if parent == branch:
- parent = ""
- else:
- fullBranch = self.projectName + branch
- if fullBranch not in self.p4BranchesInGit:
- if not self.silent:
- print("\n Importing new branch %s" % fullBranch);
- if self.importNewBranch(branch, change - 1):
- parent = ""
- self.p4BranchesInGit.append(fullBranch)
- if not self.silent:
- print("\n Resuming with change %s" % change);
-
- if self.verbose:
- print "parent determined through known branches: %s" % parent
-
- branch = self.gitRefForBranch(branch)
- parent = self.gitRefForBranch(parent)
-
- if self.verbose:
- print "looking for initial parent for %s; current parent is %s" % (branch, parent)
-
- if len(parent) == 0 and branch in self.initialParents:
- parent = self.initialParents[branch]
- del self.initialParents[branch]
-
- blob = None
- if len(parent) > 0:
- tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
- if self.verbose:
- print "Creating temporary branch: " + tempBranch
- self.commit(description, filesForCommit, tempBranch, [branchPrefix])
- self.tempBranches.append(tempBranch)
- self.checkpoint()
- blob = self.searchParent(parent, branch, tempBranch)
- if blob:
- self.commit(description, filesForCommit, branch, [branchPrefix], blob)
- else:
- if self.verbose:
- print "Parent of %s not found. Committing into head of %s" % (branch, parent)
- self.commit(description, filesForCommit, branch, [branchPrefix], parent)
- else:
- files = self.extractFilesFromCommit(description)
- self.commit(description, files, self.branch, self.depotPaths,
- self.initialParent)
- self.initialParent = ""
- except IOError:
- print self.gitError.read()
- sys.exit(1)
-
- def importHeadRevision(self, revision):
- print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
-
- details = {}
- details["user"] = "git perforce import user"
- details["desc"] = ("Initial import of %s from the state at revision %s\n"
- % (' '.join(self.depotPaths), revision))
- details["change"] = revision
- newestRevision = 0
-
- fileCnt = 0
- fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
-
- for info in p4CmdList(["files"] + fileArgs):
-
- if 'code' in info and info['code'] == 'error':
- sys.stderr.write("p4 returned an error: %s\n"
- % info['data'])
- if info['data'].find("must refer to client") >= 0:
- sys.stderr.write("This particular p4 error is misleading.\n")
- sys.stderr.write("Perhaps the depot path was misspelled.\n");
- sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
- sys.exit(1)
- if 'p4ExitCode' in info:
- sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
- sys.exit(1)
-
-
- change = int(info["change"])
- if change > newestRevision:
- newestRevision = change
-
- if info["action"] in self.delete_actions:
- # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
- #fileCnt = fileCnt + 1
- continue
-
- for prop in ["depotFile", "rev", "action", "type" ]:
- details["%s%s" % (prop, fileCnt)] = info[prop]
-
- fileCnt = fileCnt + 1
-
- details["change"] = newestRevision
-
- # Use time from top-most change so that all git-p4 clones of
- # the same p4 repo have the same commit SHA1s.
- res = p4CmdList("describe -s %d" % newestRevision)
- newestTime = None
- for r in res:
- if r.has_key('time'):
- newestTime = int(r['time'])
- if newestTime is None:
- die("\"describe -s\" on newest change %d did not give a time")
- details["time"] = newestTime
-
- self.updateOptionDict(details)
- try:
- self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
- except IOError:
- print "IO error with git fast-import. Is your git version recent enough?"
- print self.gitError.read()
-
-
- def run(self, args):
- self.depotPaths = []
- self.changeRange = ""
- self.initialParent = ""
- self.previousDepotPaths = []
-
- # map from branch depot path to parent branch
- self.knownBranches = {}
- self.initialParents = {}
- self.hasOrigin = originP4BranchesExist()
- if not self.syncWithOrigin:
- self.hasOrigin = False
-
- if self.importIntoRemotes:
- self.refPrefix = "refs/remotes/p4/"
- else:
- self.refPrefix = "refs/heads/p4/"
-
- if self.syncWithOrigin and self.hasOrigin:
- if not self.silent:
- print "Syncing with origin first by calling git fetch origin"
- system("git fetch origin")
-
- if len(self.branch) == 0:
- self.branch = self.refPrefix + "master"
- if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
- system("git update-ref %s refs/heads/p4" % self.branch)
- system("git branch -D p4");
- # create it /after/ importing, when master exists
- if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
- system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
-
- # accept either the command-line option, or the configuration variable
- if self.useClientSpec:
- # will use this after clone to set the variable
- self.useClientSpec_from_options = True
- else:
- if gitConfig("git-p4.useclientspec", "--bool") == "true":
- self.useClientSpec = True
- if self.useClientSpec:
- self.clientSpecDirs = getClientSpec()
-
- # TODO: should always look at previous commits,
- # merge with previous imports, if possible.
- if args == []:
- if self.hasOrigin:
- createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
- self.listExistingP4GitBranches()
-
- if len(self.p4BranchesInGit) > 1:
- if not self.silent:
- print "Importing from/into multiple branches"
- self.detectBranches = True
-
- if self.verbose:
- print "branches: %s" % self.p4BranchesInGit
-
- p4Change = 0
- for branch in self.p4BranchesInGit:
- logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
-
- settings = extractSettingsGitLog(logMsg)
-
- self.readOptions(settings)
- if (settings.has_key('depot-paths')
- and settings.has_key ('change')):
- change = int(settings['change']) + 1
- p4Change = max(p4Change, change)
-
- depotPaths = sorted(settings['depot-paths'])
- if self.previousDepotPaths == []:
- self.previousDepotPaths = depotPaths
- else:
- paths = []
- for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
- prev_list = prev.split("/")
- cur_list = cur.split("/")
- for i in range(0, min(len(cur_list), len(prev_list))):
- if cur_list[i] <> prev_list[i]:
- i = i - 1
- break
-
- paths.append ("/".join(cur_list[:i + 1]))
-
- self.previousDepotPaths = paths
-
- if p4Change > 0:
- self.depotPaths = sorted(self.previousDepotPaths)
- self.changeRange = "@%s,#head" % p4Change
- if not self.detectBranches:
- self.initialParent = parseRevision(self.branch)
- if not self.silent and not self.detectBranches:
- print "Performing incremental import into %s git branch" % self.branch
-
- if not self.branch.startswith("refs/"):
- self.branch = "refs/heads/" + self.branch
-
- if len(args) == 0 and self.depotPaths:
- if not self.silent:
- print "Depot paths: %s" % ' '.join(self.depotPaths)
- else:
- if self.depotPaths and self.depotPaths != args:
- print ("previous import used depot path %s and now %s was specified. "
- "This doesn't work!" % (' '.join (self.depotPaths),
- ' '.join (args)))
- sys.exit(1)
-
- self.depotPaths = sorted(args)
-
- revision = ""
- self.users = {}
-
- # Make sure no revision specifiers are used when --changesfile
- # is specified.
- bad_changesfile = False
- if len(self.changesFile) > 0:
- for p in self.depotPaths:
- if p.find("@") >= 0 or p.find("#") >= 0:
- bad_changesfile = True
- break
- if bad_changesfile:
- die("Option --changesfile is incompatible with revision specifiers")
-
- newPaths = []
- for p in self.depotPaths:
- if p.find("@") != -1:
- atIdx = p.index("@")
- self.changeRange = p[atIdx:]
- if self.changeRange == "@all":
- self.changeRange = ""
- elif ',' not in self.changeRange:
- revision = self.changeRange
- self.changeRange = ""
- p = p[:atIdx]
- elif p.find("#") != -1:
- hashIdx = p.index("#")
- revision = p[hashIdx:]
- p = p[:hashIdx]
- elif self.previousDepotPaths == []:
- # pay attention to changesfile, if given, else import
- # the entire p4 tree at the head revision
- if len(self.changesFile) == 0:
- revision = "#head"
-
- p = re.sub ("\.\.\.$", "", p)
- if not p.endswith("/"):
- p += "/"
-
- newPaths.append(p)
-
- self.depotPaths = newPaths
-
-
- self.loadUserMapFromCache()
- self.labels = {}
- if self.detectLabels:
- self.getLabels();
-
- if self.detectBranches:
- ## FIXME - what's a P4 projectName ?
- self.projectName = self.guessProjectName()
-
- if self.hasOrigin:
- self.getBranchMappingFromGitBranches()
- else:
- self.getBranchMapping()
- if self.verbose:
- print "p4-git branches: %s" % self.p4BranchesInGit
- print "initial parents: %s" % self.initialParents
- for b in self.p4BranchesInGit:
- if b != "master":
-
- ## FIXME
- b = b[len(self.projectName):]
- self.createdBranches.add(b)
-
- self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
-
- importProcess = subprocess.Popen(["git", "fast-import"],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE);
- self.gitOutput = importProcess.stdout
- self.gitStream = importProcess.stdin
- self.gitError = importProcess.stderr
-
- if revision:
- self.importHeadRevision(revision)
- else:
- changes = []
-
- if len(self.changesFile) > 0:
- output = open(self.changesFile).readlines()
- changeSet = set()
- for line in output:
- changeSet.add(int(line))
-
- for change in changeSet:
- changes.append(change)
-
- changes.sort()
- else:
- # catch "git-p4 sync" with no new branches, in a repo that
- # does not have any existing git-p4 branches
- if len(args) == 0 and not self.p4BranchesInGit:
- die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
- if self.verbose:
- print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
- self.changeRange)
- changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
-
- if len(self.maxChanges) > 0:
- changes = changes[:min(int(self.maxChanges), len(changes))]
-
- if len(changes) == 0:
- if not self.silent:
- print "No changes to import!"
- return True
-
- if not self.silent and not self.detectBranches:
- print "Import destination: %s" % self.branch
-
- self.updatedBranches = set()
-
- self.importChanges(changes)
-
- if not self.silent:
- print ""
- if len(self.updatedBranches) > 0:
- sys.stdout.write("Updated branches: ")
- for b in self.updatedBranches:
- sys.stdout.write("%s " % b)
- sys.stdout.write("\n")
-
- self.gitStream.close()
- if importProcess.wait() != 0:
- die("fast-import failed: %s" % self.gitError.read())
- self.gitOutput.close()
- self.gitError.close()
-
- # Cleanup temporary branches created during import
- if self.tempBranches != []:
- for branch in self.tempBranches:
- read_pipe("git update-ref -d %s" % branch)
- os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
-
- return True
-
-class P4Rebase(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [ ]
- self.description = ("Fetches the latest revision from perforce and "
- + "rebases the current work (branch) against it")
- self.verbose = False
-
- def run(self, args):
- sync = P4Sync()
- sync.run([])
-
- return self.rebase()
-
- def rebase(self):
- if os.system("git update-index --refresh") != 0:
- die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
- if len(read_pipe("git diff-index HEAD --")) > 0:
- die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
-
- [upstream, settings] = findUpstreamBranchPoint()
- if len(upstream) == 0:
- die("Cannot find upstream branchpoint for rebase")
-
- # the branchpoint may be p4/foo~3, so strip off the parent
- upstream = re.sub("~[0-9]+$", "", upstream)
-
- print "Rebasing the current branch onto %s" % upstream
- oldHead = read_pipe("git rev-parse HEAD").strip()
- system("git rebase %s" % upstream)
- system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
- return True
-
-class P4Clone(P4Sync):
- def __init__(self):
- P4Sync.__init__(self)
- self.description = "Creates a new git repository and imports from Perforce into it"
- self.usage = "usage: %prog [options] //depot/path[@revRange]"
- self.options += [
- optparse.make_option("--destination", dest="cloneDestination",
- action='store', default=None,
- help="where to leave result of the clone"),
- optparse.make_option("-/", dest="cloneExclude",
- action="append", type="string",
- help="exclude depot path"),
- optparse.make_option("--bare", dest="cloneBare",
- action="store_true", default=False),
- ]
- self.cloneDestination = None
- self.needsGit = False
- self.cloneBare = False
-
- # This is required for the "append" cloneExclude action
- def ensure_value(self, attr, value):
- if not hasattr(self, attr) or getattr(self, attr) is None:
- setattr(self, attr, value)
- return getattr(self, attr)
-
- def defaultDestination(self, args):
- ## TODO: use common prefix of args?
- depotPath = args[0]
- depotDir = re.sub("(@[^@]*)$", "", depotPath)
- depotDir = re.sub("(#[^#]*)$", "", depotDir)
- depotDir = re.sub(r"\.\.\.$", "", depotDir)
- depotDir = re.sub(r"/$", "", depotDir)
- return os.path.split(depotDir)[1]
-
- def run(self, args):
- if len(args) < 1:
- return False
-
- if self.keepRepoPath and not self.cloneDestination:
- sys.stderr.write("Must specify destination for --keep-path\n")
- sys.exit(1)
-
- depotPaths = args
-
- if not self.cloneDestination and len(depotPaths) > 1:
- self.cloneDestination = depotPaths[-1]
- depotPaths = depotPaths[:-1]
-
- self.cloneExclude = ["/"+p for p in self.cloneExclude]
- for p in depotPaths:
- if not p.startswith("//"):
- return False
-
- if not self.cloneDestination:
- self.cloneDestination = self.defaultDestination(args)
-
- print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
-
- if not os.path.exists(self.cloneDestination):
- os.makedirs(self.cloneDestination)
- chdir(self.cloneDestination)
-
- init_cmd = [ "git", "init" ]
- if self.cloneBare:
- init_cmd.append("--bare")
- subprocess.check_call(init_cmd)
-
- if not P4Sync.run(self, depotPaths):
- return False
- if self.branch != "master":
- if self.importIntoRemotes:
- masterbranch = "refs/remotes/p4/master"
- else:
- masterbranch = "refs/heads/p4/master"
- if gitBranchExists(masterbranch):
- system("git branch master %s" % masterbranch)
- if not self.cloneBare:
- system("git checkout -f")
- else:
- print "Could not detect main branch. No checkout/master branch created."
-
- # auto-set this variable if invoked with --use-client-spec
- if self.useClientSpec_from_options:
- system("git config --bool git-p4.useclientspec true")
-
- return True
-
-class P4Branches(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [ ]
- self.description = ("Shows the git branches that hold imports and their "
- + "corresponding perforce depot paths")
- self.verbose = False
-
- def run(self, args):
- if originP4BranchesExist():
- createOrUpdateBranchesFromOrigin()
-
- cmdline = "git rev-parse --symbolic "
- cmdline += " --remotes"
-
- for line in read_pipe_lines(cmdline):
- line = line.strip()
-
- if not line.startswith('p4/') or line == "p4/HEAD":
- continue
- branch = line
-
- log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
- settings = extractSettingsGitLog(log)
-
- print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
- return True
-
-class HelpFormatter(optparse.IndentedHelpFormatter):
- def __init__(self):
- optparse.IndentedHelpFormatter.__init__(self)
-
- def format_description(self, description):
- if description:
- return description + "\n"
- else:
- return ""
-
-def printUsage(commands):
- print "usage: %s <command> [options]" % sys.argv[0]
- print ""
- print "valid commands: %s" % ", ".join(commands)
- print ""
- print "Try %s <command> --help for command specific help." % sys.argv[0]
- print ""
-
-commands = {
- "debug" : P4Debug,
- "submit" : P4Submit,
- "commit" : P4Submit,
- "sync" : P4Sync,
- "rebase" : P4Rebase,
- "clone" : P4Clone,
- "rollback" : P4RollBack,
- "branches" : P4Branches
-}
-
-
-def main():
- if len(sys.argv[1:]) == 0:
- printUsage(commands.keys())
- sys.exit(2)
-
- cmd = ""
- cmdName = sys.argv[1]
- try:
- klass = commands[cmdName]
- cmd = klass()
- except KeyError:
- print "unknown command %s" % cmdName
- print ""
- printUsage(commands.keys())
- sys.exit(2)
-
- options = cmd.options
- cmd.gitdir = os.environ.get("GIT_DIR", None)
-
- args = sys.argv[2:]
-
- if len(options) > 0:
- if cmd.needsGit:
- options.append(optparse.make_option("--git-dir", dest="gitdir"))
-
- parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
- options,
- description = cmd.description,
- formatter = HelpFormatter())
-
- (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
- global verbose
- verbose = cmd.verbose
- if cmd.needsGit:
- if cmd.gitdir == None:
- cmd.gitdir = os.path.abspath(".git")
- if not isValidGitDir(cmd.gitdir):
- cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
- if os.path.exists(cmd.gitdir):
- cdup = read_pipe("git rev-parse --show-cdup").strip()
- if len(cdup) > 0:
- chdir(cdup);
-
- if not isValidGitDir(cmd.gitdir):
- if isValidGitDir(cmd.gitdir + "/.git"):
- cmd.gitdir += "/.git"
- else:
- die("fatal: cannot locate git repository at %s" % cmd.gitdir)
-
- os.environ["GIT_DIR"] = cmd.gitdir
-
- if not cmd.run(args):
- parser.print_help()
- sys.exit(2)
-
-
-if __name__ == '__main__':
- main()
--- /dev/null
+The git-p4 script moved to the top-level of the git source directory.
+
+Invoke it as any other git command, like "git p4 clone", for instance.
+
+Note that the top-level git-p4.py script is now the source. It is
+built using make to git-p4, which will be installed.
+
+Windows users can copy the git-p4.py source script directly, possibly
+invoking it through a batch file called "git-p4.bat" in the same folder.
+It should contain just one line:
+
+ @python "%~d0%~p0git-p4.py" %*
+++ /dev/null
-@python "%~d0%~p0git-p4" %*
--- /dev/null
+*~
+git-subtree.xml
+git-subtree.1
+mainline
+subproj
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+HOW TO INSTALL git-subtree
+==========================
+
+First, build from the top source directory.
+
+Then, in contrib/subtree, run:
+
+ make
+ make install
+ make install-doc
+
+If you used configure to do the main build the git-subtree build will
+pick up those settings. If not, you will likely have to provide a
+value for prefix:
+
+ make prefix=<some dir>
+ make prefix=<some dir> install
+ make prefix=<some dir> install-doc
+
+To run tests first copy git-subtree to the main build area so the
+newly-built git can find it:
+
+ cp git-subtree ../..
+
+Then:
+
+ make test
+
--- /dev/null
+-include ../../config.mak.autogen
+-include ../../config.mak
+
+prefix ?= /usr/local
+mandir ?= $(prefix)/share/man
+libexecdir ?= $(prefix)/libexec/git-core
+gitdir ?= $(shell git --exec-path)
+man1dir ?= $(mandir)/man1
+
+gitver ?= $(word 3,$(shell git --version))
+
+# this should be set to a 'standard' bsd-type install program
+INSTALL ?= install
+
+ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
+MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl
+
+GIT_SUBTREE_SH := git-subtree.sh
+GIT_SUBTREE := git-subtree
+
+GIT_SUBTREE_DOC := git-subtree.1
+GIT_SUBTREE_XML := git-subtree.xml
+GIT_SUBTREE_TXT := git-subtree.txt
+
+all: $(GIT_SUBTREE)
+
+$(GIT_SUBTREE): $(GIT_SUBTREE_SH)
+ cp $< $@ && chmod +x $@
+
+doc: $(GIT_SUBTREE_DOC)
+
+install: $(GIT_SUBTREE)
+ $(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir)
+
+install-doc: install-man
+
+install-man: $(GIT_SUBTREE_DOC)
+ $(INSTALL) -m 644 $^ $(man1dir)
+
+$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
+ xmlto -m $(MANPAGE_NORMAL_XSL) man $^
+
+$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
+ asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(gitver) $^
+
+test:
+ $(MAKE) -C t/ test
+
+clean:
+ rm -f *~ *.xml *.html *.1
+ rm -rf subproj mainline
--- /dev/null
+
+Please read git-subtree.txt for documentation.
+
+Please don't contact me using github mail; it's slow, ugly, and worst of
+all, redundant. Email me instead at apenwarr@gmail.com and I'll be happy to
+help.
+
+Avery
--- /dev/null
+#!/bin/bash
+#
+# git-subtree.sh: split/join git repositories in subdirectories of this one
+#
+# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
+#
+if [ $# -eq 0 ]; then
+ set -- -h
+fi
+OPTS_SPEC="\
+git subtree add --prefix=<prefix> <commit>
+git subtree merge --prefix=<prefix> <commit>
+git subtree pull --prefix=<prefix> <repository> <refspec...>
+git subtree push --prefix=<prefix> <repository> <refspec...>
+git subtree split --prefix=<prefix> <commit...>
+--
+h,help show the help
+q quiet
+d show debug messages
+P,prefix= the name of the subdir to split out
+m,message= use the given message as the commit message for the merge commit
+ options for 'split'
+annotate= add a prefix to commit message of new commits
+b,branch= create a new branch from the split subtree
+ignore-joins ignore prior --rejoin commits
+onto= try connecting new tree to an existing one
+rejoin merge the new branch back into HEAD
+ options for 'add', 'merge', 'pull' and 'push'
+squash merge subtree changes as a single commit
+"
+eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+PATH=$PATH:$(git --exec-path)
+. git-sh-setup
+
+require_work_tree
+
+quiet=
+branch=
+debug=
+command=
+onto=
+rejoin=
+ignore_joins=
+annotate=
+squash=
+message=
+
+debug()
+{
+ if [ -n "$debug" ]; then
+ echo "$@" >&2
+ fi
+}
+
+say()
+{
+ if [ -z "$quiet" ]; then
+ echo "$@" >&2
+ fi
+}
+
+assert()
+{
+ if "$@"; then
+ :
+ else
+ die "assertion failed: " "$@"
+ fi
+}
+
+
+#echo "Options: $*"
+
+while [ $# -gt 0 ]; do
+ opt="$1"
+ shift
+ case "$opt" in
+ -q) quiet=1 ;;
+ -d) debug=1 ;;
+ --annotate) annotate="$1"; shift ;;
+ --no-annotate) annotate= ;;
+ -b) branch="$1"; shift ;;
+ -P) prefix="$1"; shift ;;
+ -m) message="$1"; shift ;;
+ --no-prefix) prefix= ;;
+ --onto) onto="$1"; shift ;;
+ --no-onto) onto= ;;
+ --rejoin) rejoin=1 ;;
+ --no-rejoin) rejoin= ;;
+ --ignore-joins) ignore_joins=1 ;;
+ --no-ignore-joins) ignore_joins= ;;
+ --squash) squash=1 ;;
+ --no-squash) squash= ;;
+ --) break ;;
+ *) die "Unexpected option: $opt" ;;
+ esac
+done
+
+command="$1"
+shift
+case "$command" in
+ add|merge|pull) default= ;;
+ split|push) default="--default HEAD" ;;
+ *) die "Unknown command '$command'" ;;
+esac
+
+if [ -z "$prefix" ]; then
+ die "You must provide the --prefix option."
+fi
+
+case "$command" in
+ add) [ -e "$prefix" ] &&
+ die "prefix '$prefix' already exists." ;;
+ *) [ -e "$prefix" ] ||
+ die "'$prefix' does not exist; use 'git subtree add'" ;;
+esac
+
+dir="$(dirname "$prefix/.")"
+
+if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
+ if [ -n "$dirs" ]; then
+ die "Error: Use --prefix instead of bare filenames."
+ fi
+fi
+
+debug "command: {$command}"
+debug "quiet: {$quiet}"
+debug "revs: {$revs}"
+debug "dir: {$dir}"
+debug "opts: {$*}"
+debug
+
+cache_setup()
+{
+ cachedir="$GIT_DIR/subtree-cache/$$"
+ rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
+ mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
+ mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
+ debug "Using cachedir: $cachedir" >&2
+}
+
+cache_get()
+{
+ for oldrev in $*; do
+ if [ -r "$cachedir/$oldrev" ]; then
+ read newrev <"$cachedir/$oldrev"
+ echo $newrev
+ fi
+ done
+}
+
+cache_miss()
+{
+ for oldrev in $*; do
+ if [ ! -r "$cachedir/$oldrev" ]; then
+ echo $oldrev
+ fi
+ done
+}
+
+check_parents()
+{
+ missed=$(cache_miss $*)
+ for miss in $missed; do
+ if [ ! -r "$cachedir/notree/$miss" ]; then
+ debug " incorrect order: $miss"
+ fi
+ done
+}
+
+set_notree()
+{
+ echo "1" > "$cachedir/notree/$1"
+}
+
+cache_set()
+{
+ oldrev="$1"
+ newrev="$2"
+ if [ "$oldrev" != "latest_old" \
+ -a "$oldrev" != "latest_new" \
+ -a -e "$cachedir/$oldrev" ]; then
+ die "cache for $oldrev already exists!"
+ fi
+ echo "$newrev" >"$cachedir/$oldrev"
+}
+
+rev_exists()
+{
+ if git rev-parse "$1" >/dev/null 2>&1; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+rev_is_descendant_of_branch()
+{
+ newrev="$1"
+ branch="$2"
+ branch_hash=$(git rev-parse $branch)
+ match=$(git rev-list -1 $branch_hash ^$newrev)
+
+ if [ -z "$match" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# if a commit doesn't have a parent, this might not work. But we only want
+# to remove the parent from the rev-list, and since it doesn't exist, it won't
+# be there anyway, so do nothing in that case.
+try_remove_previous()
+{
+ if rev_exists "$1^"; then
+ echo "^$1^"
+ fi
+}
+
+find_latest_squash()
+{
+ debug "Looking for latest squash ($dir)..."
+ dir="$1"
+ sq=
+ main=
+ sub=
+ git log --grep="^git-subtree-dir: $dir/*\$" \
+ --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+ while read a b junk; do
+ debug "$a $b $junk"
+ debug "{{$sq/$main/$sub}}"
+ case "$a" in
+ START) sq="$b" ;;
+ git-subtree-mainline:) main="$b" ;;
+ git-subtree-split:) sub="$b" ;;
+ END)
+ if [ -n "$sub" ]; then
+ if [ -n "$main" ]; then
+ # a rejoin commit?
+ # Pretend its sub was a squash.
+ sq="$sub"
+ fi
+ debug "Squash found: $sq $sub"
+ echo "$sq" "$sub"
+ break
+ fi
+ sq=
+ main=
+ sub=
+ ;;
+ esac
+ done
+}
+
+find_existing_splits()
+{
+ debug "Looking for prior splits..."
+ dir="$1"
+ revs="$2"
+ main=
+ sub=
+ git log --grep="^git-subtree-dir: $dir/*\$" \
+ --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
+ while read a b junk; do
+ case "$a" in
+ START) sq="$b" ;;
+ git-subtree-mainline:) main="$b" ;;
+ git-subtree-split:) sub="$b" ;;
+ END)
+ debug " Main is: '$main'"
+ if [ -z "$main" -a -n "$sub" ]; then
+ # squash commits refer to a subtree
+ debug " Squash: $sq from $sub"
+ cache_set "$sq" "$sub"
+ fi
+ if [ -n "$main" -a -n "$sub" ]; then
+ debug " Prior: $main -> $sub"
+ cache_set $main $sub
+ cache_set $sub $sub
+ try_remove_previous "$main"
+ try_remove_previous "$sub"
+ fi
+ main=
+ sub=
+ ;;
+ esac
+ done
+}
+
+copy_commit()
+{
+ # We're going to set some environment vars here, so
+ # do it in a subshell to get rid of them safely later
+ debug copy_commit "{$1}" "{$2}" "{$3}"
+ git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
+ (
+ read GIT_AUTHOR_NAME
+ read GIT_AUTHOR_EMAIL
+ read GIT_AUTHOR_DATE
+ read GIT_COMMITTER_NAME
+ read GIT_COMMITTER_EMAIL
+ read GIT_COMMITTER_DATE
+ export GIT_AUTHOR_NAME \
+ GIT_AUTHOR_EMAIL \
+ GIT_AUTHOR_DATE \
+ GIT_COMMITTER_NAME \
+ GIT_COMMITTER_EMAIL \
+ GIT_COMMITTER_DATE
+ (echo -n "$annotate"; cat ) |
+ git commit-tree "$2" $3 # reads the rest of stdin
+ ) || die "Can't copy commit $1"
+}
+
+add_msg()
+{
+ dir="$1"
+ latest_old="$2"
+ latest_new="$3"
+ if [ -n "$message" ]; then
+ commit_message="$message"
+ else
+ commit_message="Add '$dir/' from commit '$latest_new'"
+ fi
+ cat <<-EOF
+ $commit_message
+
+ git-subtree-dir: $dir
+ git-subtree-mainline: $latest_old
+ git-subtree-split: $latest_new
+ EOF
+}
+
+add_squashed_msg()
+{
+ if [ -n "$message" ]; then
+ echo "$message"
+ else
+ echo "Merge commit '$1' as '$2'"
+ fi
+}
+
+rejoin_msg()
+{
+ dir="$1"
+ latest_old="$2"
+ latest_new="$3"
+ if [ -n "$message" ]; then
+ commit_message="$message"
+ else
+ commit_message="Split '$dir/' into commit '$latest_new'"
+ fi
+ cat <<-EOF
+ $commit_message
+
+ git-subtree-dir: $dir
+ git-subtree-mainline: $latest_old
+ git-subtree-split: $latest_new
+ EOF
+}
+
+squash_msg()
+{
+ dir="$1"
+ oldsub="$2"
+ newsub="$3"
+ newsub_short=$(git rev-parse --short "$newsub")
+
+ if [ -n "$oldsub" ]; then
+ oldsub_short=$(git rev-parse --short "$oldsub")
+ echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
+ echo
+ git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
+ git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
+ else
+ echo "Squashed '$dir/' content from commit $newsub_short"
+ fi
+
+ echo
+ echo "git-subtree-dir: $dir"
+ echo "git-subtree-split: $newsub"
+}
+
+toptree_for_commit()
+{
+ commit="$1"
+ git log -1 --pretty=format:'%T' "$commit" -- || exit $?
+}
+
+subtree_for_commit()
+{
+ commit="$1"
+ dir="$2"
+ git ls-tree "$commit" -- "$dir" |
+ while read mode type tree name; do
+ assert [ "$name" = "$dir" ]
+ assert [ "$type" = "tree" -o "$type" = "commit" ]
+ [ "$type" = "commit" ] && continue # ignore submodules
+ echo $tree
+ break
+ done
+}
+
+tree_changed()
+{
+ tree=$1
+ shift
+ if [ $# -ne 1 ]; then
+ return 0 # weird parents, consider it changed
+ else
+ ptree=$(toptree_for_commit $1)
+ if [ "$ptree" != "$tree" ]; then
+ return 0 # changed
+ else
+ return 1 # not changed
+ fi
+ fi
+}
+
+new_squash_commit()
+{
+ old="$1"
+ oldsub="$2"
+ newsub="$3"
+ tree=$(toptree_for_commit $newsub) || exit $?
+ if [ -n "$old" ]; then
+ squash_msg "$dir" "$oldsub" "$newsub" |
+ git commit-tree "$tree" -p "$old" || exit $?
+ else
+ squash_msg "$dir" "" "$newsub" |
+ git commit-tree "$tree" || exit $?
+ fi
+}
+
+copy_or_skip()
+{
+ rev="$1"
+ tree="$2"
+ newparents="$3"
+ assert [ -n "$tree" ]
+
+ identical=
+ nonidentical=
+ p=
+ gotparents=
+ for parent in $newparents; do
+ ptree=$(toptree_for_commit $parent) || exit $?
+ [ -z "$ptree" ] && continue
+ if [ "$ptree" = "$tree" ]; then
+ # an identical parent could be used in place of this rev.
+ identical="$parent"
+ else
+ nonidentical="$parent"
+ fi
+
+ # sometimes both old parents map to the same newparent;
+ # eliminate duplicates
+ is_new=1
+ for gp in $gotparents; do
+ if [ "$gp" = "$parent" ]; then
+ is_new=
+ break
+ fi
+ done
+ if [ -n "$is_new" ]; then
+ gotparents="$gotparents $parent"
+ p="$p -p $parent"
+ fi
+ done
+
+ if [ -n "$identical" ]; then
+ echo $identical
+ else
+ copy_commit $rev $tree "$p" || exit $?
+ fi
+}
+
+ensure_clean()
+{
+ if ! git diff-index HEAD --exit-code --quiet 2>&1; then
+ die "Working tree has modifications. Cannot add."
+ fi
+ if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
+ die "Index has modifications. Cannot add."
+ fi
+}
+
+cmd_add()
+{
+ if [ -e "$dir" ]; then
+ die "'$dir' already exists. Cannot add."
+ fi
+
+ ensure_clean
+
+ if [ $# -eq 1 ]; then
+ "cmd_add_commit" "$@"
+ elif [ $# -eq 2 ]; then
+ "cmd_add_repository" "$@"
+ else
+ say "error: parameters were '$@'"
+ die "Provide either a refspec or a repository and refspec."
+ fi
+}
+
+cmd_add_repository()
+{
+ echo "git fetch" "$@"
+ repository=$1
+ refspec=$2
+ git fetch "$@" || exit $?
+ revs=FETCH_HEAD
+ set -- $revs
+ cmd_add_commit "$@"
+}
+
+cmd_add_commit()
+{
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ set -- $revs
+ rev="$1"
+
+ debug "Adding $dir as '$rev'..."
+ git read-tree --prefix="$dir" $rev || exit $?
+ git checkout -- "$dir" || exit $?
+ tree=$(git write-tree) || exit $?
+
+ headrev=$(git rev-parse HEAD) || exit $?
+ if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
+ headp="-p $headrev"
+ else
+ headp=
+ fi
+
+ if [ -n "$squash" ]; then
+ rev=$(new_squash_commit "" "" "$rev") || exit $?
+ commit=$(add_squashed_msg "$rev" "$dir" |
+ git commit-tree $tree $headp -p "$rev") || exit $?
+ else
+ commit=$(add_msg "$dir" "$headrev" "$rev" |
+ git commit-tree $tree $headp -p "$rev") || exit $?
+ fi
+ git reset "$commit" || exit $?
+
+ say "Added dir '$dir'"
+}
+
+cmd_split()
+{
+ debug "Splitting $dir..."
+ cache_setup || exit $?
+
+ if [ -n "$onto" ]; then
+ debug "Reading history for --onto=$onto..."
+ git rev-list $onto |
+ while read rev; do
+ # the 'onto' history is already just the subdir, so
+ # any parent we find there can be used verbatim
+ debug " cache: $rev"
+ cache_set $rev $rev
+ done
+ fi
+
+ if [ -n "$ignore_joins" ]; then
+ unrevs=
+ else
+ unrevs="$(find_existing_splits "$dir" "$revs")"
+ fi
+
+ # We can't restrict rev-list to only $dir here, because some of our
+ # parents have the $dir contents the root, and those won't match.
+ # (and rev-list --follow doesn't seem to solve this)
+ grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
+ revmax=$(eval "$grl" | wc -l)
+ revcount=0
+ createcount=0
+ eval "$grl" |
+ while read rev parents; do
+ revcount=$(($revcount + 1))
+ say -n "$revcount/$revmax ($createcount)\r"
+ debug "Processing commit: $rev"
+ exists=$(cache_get $rev)
+ if [ -n "$exists" ]; then
+ debug " prior: $exists"
+ continue
+ fi
+ createcount=$(($createcount + 1))
+ debug " parents: $parents"
+ newparents=$(cache_get $parents)
+ debug " newparents: $newparents"
+
+ tree=$(subtree_for_commit $rev "$dir")
+ debug " tree is: $tree"
+
+ check_parents $parents
+
+ # ugly. is there no better way to tell if this is a subtree
+ # vs. a mainline commit? Does it matter?
+ if [ -z $tree ]; then
+ set_notree $rev
+ if [ -n "$newparents" ]; then
+ cache_set $rev $rev
+ fi
+ continue
+ fi
+
+ newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+ debug " newrev is: $newrev"
+ cache_set $rev $newrev
+ cache_set latest_new $newrev
+ cache_set latest_old $rev
+ done || exit $?
+ latest_new=$(cache_get latest_new)
+ if [ -z "$latest_new" ]; then
+ die "No new revisions were found"
+ fi
+
+ if [ -n "$rejoin" ]; then
+ debug "Merging split branch into HEAD..."
+ latest_old=$(cache_get latest_old)
+ git merge -s ours \
+ -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+ $latest_new >&2 || exit $?
+ fi
+ if [ -n "$branch" ]; then
+ if rev_exists "refs/heads/$branch"; then
+ if ! rev_is_descendant_of_branch $latest_new $branch; then
+ die "Branch '$branch' is not an ancestor of commit '$latest_new'."
+ fi
+ action='Updated'
+ else
+ action='Created'
+ fi
+ git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
+ say "$action branch '$branch'"
+ fi
+ echo $latest_new
+ exit 0
+}
+
+cmd_merge()
+{
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ ensure_clean
+
+ set -- $revs
+ if [ $# -ne 1 ]; then
+ die "You must provide exactly one revision. Got: '$revs'"
+ fi
+ rev="$1"
+
+ if [ -n "$squash" ]; then
+ first_split="$(find_latest_squash "$dir")"
+ if [ -z "$first_split" ]; then
+ die "Can't squash-merge: '$dir' was never added."
+ fi
+ set $first_split
+ old=$1
+ sub=$2
+ if [ "$sub" = "$rev" ]; then
+ say "Subtree is already at commit $rev."
+ exit 0
+ fi
+ new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
+ debug "New squash commit: $new"
+ rev="$new"
+ fi
+
+ version=$(git version)
+ if [ "$version" \< "git version 1.7" ]; then
+ if [ -n "$message" ]; then
+ git merge -s subtree --message="$message" $rev
+ else
+ git merge -s subtree $rev
+ fi
+ else
+ if [ -n "$message" ]; then
+ git merge -Xsubtree="$prefix" --message="$message" $rev
+ else
+ git merge -Xsubtree="$prefix" $rev
+ fi
+ fi
+}
+
+cmd_pull()
+{
+ ensure_clean
+ git fetch "$@" || exit $?
+ revs=FETCH_HEAD
+ set -- $revs
+ cmd_merge "$@"
+}
+
+cmd_push()
+{
+ if [ $# -ne 2 ]; then
+ die "You must provide <repository> <refspec>"
+ fi
+ if [ -e "$dir" ]; then
+ repository=$1
+ refspec=$2
+ echo "git push using: " $repository $refspec
+ git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+ else
+ die "'$dir' must already exist. Try 'git subtree add'."
+ fi
+}
+
+"cmd_$command" "$@"
--- /dev/null
+git-subtree(1)
+==============
+
+NAME
+----
+git-subtree - Merge subtrees together and split repository into subtrees
+
+
+SYNOPSIS
+--------
+[verse]
+'git subtree' add -P <prefix> <commit>
+'git subtree' pull -P <prefix> <repository> <refspec...>
+'git subtree' push -P <prefix> <repository> <refspec...>
+'git subtree' merge -P <prefix> <commit>
+'git subtree' split -P <prefix> [OPTIONS] [<commit>]
+
+
+DESCRIPTION
+-----------
+Subtrees allow subprojects to be included within a subdirectory
+of the main project, optionally including the subproject's
+entire history.
+
+For example, you could include the source code for a library
+as a subdirectory of your application.
+
+Subtrees are not to be confused with submodules, which are meant for
+the same task. Unlike submodules, subtrees do not need any special
+constructions (like .gitmodule files or gitlinks) be present in
+your repository, and do not force end-users of your
+repository to do anything special or to understand how subtrees
+work. A subtree is just a subdirectory that can be
+committed to, branched, and merged along with your project in
+any way you want.
+
+They are also not to be confused with using the subtree merge
+strategy. The main difference is that, besides merging
+the other project as a subdirectory, you can also extract the
+entire history of a subdirectory from your project and make it
+into a standalone project. Unlike the subtree merge strategy
+you can alternate back and forth between these
+two operations. If the standalone library gets updated, you can
+automatically merge the changes into your project; if you
+update the library inside your project, you can "split" the
+changes back out again and merge them back into the library
+project.
+
+For example, if a library you made for one application ends up being
+useful elsewhere, you can extract its entire history and publish
+that as its own git repository, without accidentally
+intermingling the history of your application project.
+
+[TIP]
+In order to keep your commit messages clean, we recommend that
+people split their commits between the subtrees and the main
+project as much as possible. That is, if you make a change that
+affects both the library and the main application, commit it in
+two pieces. That way, when you split the library commits out
+later, their descriptions will still make sense. But if this
+isn't important to you, it's not *necessary*. git subtree will
+simply leave out the non-library-related parts of the commit
+when it splits it out into the subproject later.
+
+
+COMMANDS
+--------
+add::
+ Create the <prefix> subtree by importing its contents
+ from the given <refspec> or <repository> and remote <refspec>.
+ A new commit is created automatically, joining the imported
+ project's history with your own. With '--squash', imports
+ only a single commit from the subproject, rather than its
+ entire history.
+
+merge::
+ Merge recent changes up to <commit> into the <prefix>
+ subtree. As with normal 'git merge', this doesn't
+ remove your own local changes; it just merges those
+ changes into the latest <commit>. With '--squash',
+ creates only one commit that contains all the changes,
+ rather than merging in the entire history.
+
+ If you use '--squash', the merge direction doesn't
+ always have to be forward; you can use this command to
+ go back in time from v2.5 to v2.4, for example. If your
+ merge introduces a conflict, you can resolve it in the
+ usual ways.
+
+pull::
+ Exactly like 'merge', but parallels 'git pull' in that
+ it fetches the given commit from the specified remote
+ repository.
+
+push::
+ Does a 'split' (see above) using the <prefix> supplied
+ and then does a 'git push' to push the result to the
+ repository and refspec. This can be used to push your
+ subtree to different branches of the remote repository.
+
+split::
+ Extract a new, synthetic project history from the
+ history of the <prefix> subtree. The new history
+ includes only the commits (including merges) that
+ affected <prefix>, and each of those commits now has the
+ contents of <prefix> at the root of the project instead
+ of in a subdirectory. Thus, the newly created history
+ is suitable for export as a separate git repository.
+
+ After splitting successfully, a single commit id is
+ printed to stdout. This corresponds to the HEAD of the
+ newly created tree, which you can manipulate however you
+ want.
+
+ Repeated splits of exactly the same history are
+ guaranteed to be identical (ie. to produce the same
+ commit ids). Because of this, if you add new commits
+ and then re-split, the new commits will be attached as
+ commits on top of the history you generated last time,
+ so 'git merge' and friends will work as expected.
+
+ Note that if you use '--squash' when you merge, you
+ should usually not just '--rejoin' when you split.
+
+
+OPTIONS
+-------
+-q::
+--quiet::
+ Suppress unnecessary output messages on stderr.
+
+-d::
+--debug::
+ Produce even more unnecessary output messages on stderr.
+
+-P <prefix>::
+--prefix=<prefix>::
+ Specify the path in the repository to the subtree you
+ want to manipulate. This option is mandatory
+ for all commands.
+
+-m <message>::
+--message=<message>::
+ This option is only valid for add, merge and pull (unsure).
+ Specify <message> as the commit message for the merge commit.
+
+
+OPTIONS FOR add, merge, push, pull
+----------------------------------
+--squash::
+ This option is only valid for add, merge, push and pull
+ commands.
+
+ Instead of merging the entire history from the subtree
+ project, produce only a single commit that contains all
+ the differences you want to merge, and then merge that
+ new commit into your project.
+
+ Using this option helps to reduce log clutter. People
+ rarely want to see every change that happened between
+ v1.0 and v1.1 of the library they're using, since none of the
+ interim versions were ever included in their application.
+
+ Using '--squash' also helps avoid problems when the same
+ subproject is included multiple times in the same
+ project, or is removed and then re-added. In such a
+ case, it doesn't make sense to combine the histories
+ anyway, since it's unclear which part of the history
+ belongs to which subtree.
+
+ Furthermore, with '--squash', you can switch back and
+ forth between different versions of a subtree, rather
+ than strictly forward. 'git subtree merge --squash'
+ always adjusts the subtree to match the exactly
+ specified commit, even if getting to that commit would
+ require undoing some changes that were added earlier.
+
+ Whether or not you use '--squash', changes made in your
+ local repository remain intact and can be later split
+ and send upstream to the subproject.
+
+
+OPTIONS FOR split
+-----------------
+--annotate=<annotation>::
+ This option is only valid for the split command.
+
+ When generating synthetic history, add <annotation> as a
+ prefix to each commit message. Since we're creating new
+ commits with the same commit message, but possibly
+ different content, from the original commits, this can help
+ to differentiate them and avoid confusion.
+
+ Whenever you split, you need to use the same
+ <annotation>, or else you don't have a guarantee that
+ the new re-created history will be identical to the old
+ one. That will prevent merging from working correctly.
+ git subtree tries to make it work anyway, particularly
+ if you use --rejoin, but it may not always be effective.
+
+-b <branch>::
+--branch=<branch>::
+ This option is only valid for the split command.
+
+ After generating the synthetic history, create a new
+ branch called <branch> that contains the new history.
+ This is suitable for immediate pushing upstream.
+ <branch> must not already exist.
+
+--ignore-joins::
+ This option is only valid for the split command.
+
+ If you use '--rejoin', git subtree attempts to optimize
+ its history reconstruction to generate only the new
+ commits since the last '--rejoin'. '--ignore-join'
+ disables this behaviour, forcing it to regenerate the
+ entire history. In a large project, this can take a
+ long time.
+
+--onto=<onto>::
+ This option is only valid for the split command.
+
+ If your subtree was originally imported using something
+ other than git subtree, its history may not match what
+ git subtree is expecting. In that case, you can specify
+ the commit id <onto> that corresponds to the first
+ revision of the subproject's history that was imported
+ into your project, and git subtree will attempt to build
+ its history from there.
+
+ If you used 'git subtree add', you should never need
+ this option.
+
+--rejoin::
+ This option is only valid for the split command.
+
+ After splitting, merge the newly created synthetic
+ history back into your main project. That way, future
+ splits can search only the part of history that has
+ been added since the most recent --rejoin.
+
+ If your split commits end up merged into the upstream
+ subproject, and then you want to get the latest upstream
+ version, this will allow git's merge algorithm to more
+ intelligently avoid conflicts (since it knows these
+ synthetic commits are already part of the upstream
+ repository).
+
+ Unfortunately, using this option results in 'git log'
+ showing an extra copy of every new commit that was
+ created (the original, and the synthetic one).
+
+ If you do all your merges with '--squash', don't use
+ '--rejoin' when you split, because you don't want the
+ subproject's history to be part of your project anyway.
+
+
+EXAMPLE 1. Add command
+----------------------
+Let's assume that you have a local repository that you would like
+to add an external vendor library to. In this case we will add the
+git-subtree repository as a subdirectory of your already existing
+git-extensions repository in ~/git-extensions/:
+
+ $ git subtree add --prefix=git-subtree --squash \
+ git://github.com/apenwarr/git-subtree.git master
+
+'master' needs to be a valid remote ref and can be a different branch
+name
+
+You can omit the --squash flag, but doing so will increase the number
+of commits that are incldued in your local repository.
+
+We now have a ~/git-extensions/git-subtree directory containing code
+from the master branch of git://github.com/apenwarr/git-subtree.git
+in our git-extensions repository.
+
+EXAMPLE 2. Extract a subtree using commit, merge and pull
+---------------------------------------------------------
+Let's use the repository for the git source code as an example.
+First, get your own copy of the git.git repository:
+
+ $ git clone git://git.kernel.org/pub/scm/git/git.git test-git
+ $ cd test-git
+
+gitweb (commit 1130ef3) was merged into git as of commit
+0a8f4f0, after which it was no longer maintained separately.
+But imagine it had been maintained separately, and we wanted to
+extract git's changes to gitweb since that time, to share with
+the upstream. You could do this:
+
+ $ git subtree split --prefix=gitweb --annotate='(split) ' \
+ 0a8f4f0^.. --onto=1130ef3 --rejoin \
+ --branch gitweb-latest
+ $ gitk gitweb-latest
+ $ git push git@github.com:whatever/gitweb.git gitweb-latest:master
+
+(We use '0a8f4f0^..' because that means "all the changes from
+0a8f4f0 to the current version, including 0a8f4f0 itself.")
+
+If gitweb had originally been merged using 'git subtree add' (or
+a previous split had already been done with --rejoin specified)
+then you can do all your splits without having to remember any
+weird commit ids:
+
+ $ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
+ --branch gitweb-latest2
+
+And you can merge changes back in from the upstream project just
+as easily:
+
+ $ git subtree pull --prefix=gitweb \
+ git@github.com:whatever/gitweb.git master
+
+Or, using '--squash', you can actually rewind to an earlier
+version of gitweb:
+
+ $ git subtree merge --prefix=gitweb --squash gitweb-latest~10
+
+Then make some changes:
+
+ $ date >gitweb/myfile
+ $ git add gitweb/myfile
+ $ git commit -m 'created myfile'
+
+And fast forward again:
+
+ $ git subtree merge --prefix=gitweb --squash gitweb-latest
+
+And notice that your change is still intact:
+
+ $ ls -l gitweb/myfile
+
+And you can split it out and look at your changes versus
+the standard gitweb:
+
+ git log gitweb-latest..$(git subtree split --prefix=gitweb)
+
+EXAMPLE 3. Extract a subtree using branch
+-----------------------------------------
+Suppose you have a source directory with many files and
+subdirectories, and you want to extract the lib directory to its own
+git project. Here's a short way to do it:
+
+First, make the new repository wherever you want:
+
+ $ <go to the new location>
+ $ git init --bare
+
+Back in your original directory:
+
+ $ git subtree split --prefix=lib --annotate="(split)" -b split
+
+Then push the new branch onto the new empty repository:
+
+ $ git push <new-repo> split:master
+
+
+AUTHOR
+------
+Written by Avery Pennarun <apenwarr@gmail.com>
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
--- /dev/null
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+#GIT_TEST_OPTS=--verbose --debug
+SHELL_PATH ?= $(SHELL)
+PERL_PATH ?= /usr/bin/perl
+TAR ?= $(TAR)
+RM ?= rm -f
+PROVE ?= prove
+DEFAULT_TEST_TARGET ?= test
+
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: $(DEFAULT_TEST_TARGET)
+
+test: pre-clean $(TEST_LINT)
+ $(MAKE) aggregate-results-and-cleanup
+
+prove: pre-clean $(TEST_LINT)
+ @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+ $(MAKE) clean
+
+$(T):
+ @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+pre-clean:
+ $(RM) -r test-results
+
+clean:
+ $(RM) -r 'trash directory'.* test-results
+ $(RM) -r valgrind/bin
+ $(RM) .prove
+
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+ @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+ test -z "$$dups" || { \
+ echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+ @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+ test -z "$$bad" || { \
+ echo >&2 "non-executable tests:" $$bad; exit 1; }
+
+aggregate-results-and-cleanup: $(T)
+ $(MAKE) aggregate-results
+ $(MAKE) clean
+
+aggregate-results:
+ for f in ../../../t/test-results/t*-*.counts; do \
+ echo "$$f"; \
+ done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh
+
+valgrind:
+ $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
+
+test-results:
+ mkdir -p test-results
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2012 Avery Pennaraum
+#
+test_description='Basic porcelain support for subtrees
+
+This test verifies the basic operation of the merge, pull, add
+and split subcommands of git subtree.
+'
+
+export TEST_DIRECTORY=$(pwd)/../../../t
+
+. ../../../t/test-lib.sh
+
+create()
+{
+ echo "$1" >"$1"
+ git add "$1"
+}
+
+
+check_equal()
+{
+ test_debug 'echo'
+ test_debug "echo \"check a:\" \"{$1}\""
+ test_debug "echo \" b:\" \"{$2}\""
+ if [ "$1" = "$2" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+fixnl()
+{
+ t=""
+ while read x; do
+ t="$t$x "
+ done
+ echo $t
+}
+
+multiline()
+{
+ while read x; do
+ set -- $x
+ for d in "$@"; do
+ echo "$d"
+ done
+ done
+}
+
+undo()
+{
+ git reset --hard HEAD~
+}
+
+last_commit_message()
+{
+ git log --pretty=format:%s -1
+}
+
+# 1
+test_expect_success 'init subproj' '
+ test_create_repo subproj
+'
+
+# To the subproject!
+cd subproj
+
+# 2
+test_expect_success 'add sub1' '
+ create sub1 &&
+ git commit -m "sub1" &&
+ git branch sub1 &&
+ git branch -m master subproj
+'
+
+# 3
+test_expect_success 'add sub2' '
+ create sub2 &&
+ git commit -m "sub2" &&
+ git branch sub2
+'
+
+# 4
+test_expect_success 'add sub3' '
+ create sub3 &&
+ git commit -m "sub3" &&
+ git branch sub3
+'
+
+# Back to mainline
+cd ..
+
+# 5
+test_expect_success 'add main4' '
+ create main4 &&
+ git commit -m "main4" &&
+ git branch -m master mainline &&
+ git branch subdir
+'
+
+# 6
+test_expect_success 'fetch subproj history' '
+ git fetch ./subproj sub1 &&
+ git branch sub1 FETCH_HEAD
+'
+
+# 7
+test_expect_success 'no subtree exists in main tree' '
+ test_must_fail git subtree merge --prefix=subdir sub1
+'
+
+# 8
+test_expect_success 'no pull from non-existant subtree' '
+ test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+'
+
+# 9
+test_expect_success 'check if --message works for add' '
+ git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject" &&
+ undo
+'
+
+# 10
+test_expect_success 'check if --message works as -m and --prefix as -P' '
+ git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
+ undo
+'
+
+# 11
+test_expect_success 'check if --message works with squash too' '
+ git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
+ undo
+'
+
+# 12
+test_expect_success 'add subproj to mainline' '
+ git subtree add --prefix=subdir/ FETCH_HEAD &&
+ check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+'
+
+# 13
+# this shouldn't actually do anything, since FETCH_HEAD is already a parent
+test_expect_success 'merge fetched subproj' '
+ git merge -m "merge -s -ours" -s ours FETCH_HEAD
+'
+
+# 14
+test_expect_success 'add main-sub5' '
+ create subdir/main-sub5 &&
+ git commit -m "main-sub5"
+'
+
+# 15
+test_expect_success 'add main6' '
+ create main6 &&
+ git commit -m "main6 boring"
+'
+
+# 16
+test_expect_success 'add main-sub7' '
+ create subdir/main-sub7 &&
+ git commit -m "main-sub7"
+'
+
+# 17
+test_expect_success 'fetch new subproj history' '
+ git fetch ./subproj sub2 &&
+ git branch sub2 FETCH_HEAD
+'
+
+# 18
+test_expect_success 'check if --message works for merge' '
+ git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+ check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
+ undo
+'
+
+# 19
+test_expect_success 'check if --message for merge works with squash too' '
+ git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+ check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
+ undo
+'
+
+# 20
+test_expect_success 'merge new subproj history into subdir' '
+ git subtree merge --prefix=subdir FETCH_HEAD &&
+ git branch pre-split &&
+ check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline"
+'
+
+# 21
+test_expect_success 'Check that prefix argument is required for split' '
+ echo "You must provide the --prefix option." > expected &&
+ test_must_fail git subtree split > actual 2>&1 &&
+ test_debug "echo -n expected: " &&
+ test_debug "cat expected" &&
+ test_debug "echo -n actual: " &&
+ test_debug "cat actual" &&
+ test_cmp expected actual &&
+ rm -f expected actual
+'
+
+# 22
+test_expect_success 'Check that the <prefix> exists for a split' '
+ echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected &&
+ test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
+ test_debug "echo -n expected: " &&
+ test_debug "cat expected" &&
+ test_debug "echo -n actual: " &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+# rm -f expected actual
+'
+
+# 23
+test_expect_success 'check if --message works for split+rejoin' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ git branch spl1 "$spl1" &&
+ check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
+ undo
+'
+
+# 24
+test_expect_success 'check split with --branch' '
+ spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+ undo &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+ check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
+'
+
+# 25
+test_expect_success 'check split with --branch for an existing branch' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ undo &&
+ git branch splitbr2 sub1 &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+ check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
+'
+
+# 26
+test_expect_success 'check split with --branch for an incompatible branch' '
+ test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+'
+
+
+# 27
+test_expect_success 'check split+rejoin' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ undo &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
+ check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+'
+
+# 28
+test_expect_success 'add main-sub8' '
+ create subdir/main-sub8 &&
+ git commit -m "main-sub8"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 29
+test_expect_success 'merge split into subproj' '
+ git fetch .. spl1 &&
+ git branch spl1 FETCH_HEAD &&
+ git merge FETCH_HEAD
+'
+
+# 30
+test_expect_success 'add sub9' '
+ create sub9 &&
+ git commit -m "sub9"
+'
+
+# Back to mainline
+cd ..
+
+# 31
+test_expect_success 'split for sub8' '
+ split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"''
+ git branch split2 "$split2"
+'
+
+# 32
+test_expect_success 'add main-sub10' '
+ create subdir/main-sub10 &&
+ git commit -m "main-sub10"
+'
+
+# 33
+test_expect_success 'split for sub10' '
+ spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+ git branch spl3 "$spl3"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 34
+test_expect_success 'merge split into subproj' '
+ git fetch .. spl3 &&
+ git branch spl3 FETCH_HEAD &&
+ git merge FETCH_HEAD &&
+ git branch subproj-merge-spl3
+'
+
+chkm="main4 main6"
+chkms="main-sub10 main-sub5 main-sub7 main-sub8"
+chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
+chks="sub1 sub2 sub3 sub9"
+chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+
+# 35
+test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
+ subfiles=''"$(git ls-files | fixnl)"'' &&
+ check_equal "$subfiles" "$chkms $chks"
+'
+
+# 36
+test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
+ check_equal "$allchanges" "$chkms $chks"
+'
+
+# Back to mainline
+cd ..
+
+# 37
+test_expect_success 'pull from subproj' '
+ git fetch ./subproj subproj-merge-spl3 &&
+ git branch subproj-merge-spl3 FETCH_HEAD &&
+ git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+'
+
+# 38
+test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
+ mainfiles=''"$(git ls-files | fixnl)"'' &&
+ check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+'
+
+# 39
+test_expect_success 'make sure each filename changed exactly once in the entire history' '
+ # main-sub?? and /subdir/main-sub?? both change, because those are the
+ # changes that were split into their own history. And subdir/sub?? never
+ # change, since they were *only* changed in the subtree branch.
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
+ check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+'
+
+# 40
+test_expect_success 'make sure the --rejoin commits never make it into subproj' '
+ check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' ""
+'
+
+# 41
+test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
+ # They are meaningless to subproj since one side of the merge refers to the mainline
+ check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' ""
+'
+
+# prepare second pair of repositories
+mkdir test2
+cd test2
+
+# 42
+test_expect_success 'init main' '
+ test_create_repo main
+'
+
+cd main
+
+# 43
+test_expect_success 'add main1' '
+ create main1 &&
+ git commit -m "main1"
+'
+
+cd ..
+
+# 44
+test_expect_success 'init sub' '
+ test_create_repo sub
+'
+
+cd sub
+
+# 45
+test_expect_success 'add sub2' '
+ create sub2 &&
+ git commit -m "sub2"
+'
+
+cd ../main
+
+# check if split can find proper base without --onto
+
+# 46
+test_expect_success 'add sub as subdir in main' '
+ git fetch ../sub master &&
+ git branch sub2 FETCH_HEAD &&
+ git subtree add --prefix subdir sub2
+'
+
+cd ../sub
+
+# 47
+test_expect_success 'add sub3' '
+ create sub3 &&
+ git commit -m "sub3"
+'
+
+cd ../main
+
+# 48
+test_expect_success 'merge from sub' '
+ git fetch ../sub master &&
+ git branch sub3 FETCH_HEAD &&
+ git subtree merge --prefix subdir sub3
+'
+
+# 49
+test_expect_success 'add main-sub4' '
+ create subdir/main-sub4 &&
+ git commit -m "main-sub4"
+'
+
+# 50
+test_expect_success 'split for main-sub4 without --onto' '
+ git subtree split --prefix subdir --branch mainsub4
+'
+
+# at this point, the new commit parent should be sub3 if it is not,
+# something went wrong (the "newparent" of "master~" commit should
+# have been sub3, but it was not, because its cache was not set to
+# itself)
+
+# 51
+test_expect_success 'check that the commit parent is sub3' '
+ check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"''
+'
+
+# 52
+test_expect_success 'add main-sub5' '
+ mkdir subdir2 &&
+ create subdir2/main-sub5 &&
+ git commit -m "main-sub5"
+'
+
+# 53
+test_expect_success 'split for main-sub5 without --onto' '
+ # also test that we still can split out an entirely new subtree
+ # if the parent of the first commit in the tree is not empty,
+ # then the new subtree has accidently been attached to something
+ git subtree split --prefix subdir2 --branch mainsub5 &&
+ check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' ""
+'
+
+# make sure no patch changes more than one file. The original set of commits
+# changed only one file each. A multi-file change would imply that we pruned
+# commits too aggressively.
+joincommits()
+{
+ commit=
+ all=
+ while read x y; do
+ #echo "{$x}" >&2
+ if [ -z "$x" ]; then
+ continue
+ elif [ "$x" = "commit:" ]; then
+ if [ -n "$commit" ]; then
+ echo "$commit $all"
+ all=
+ fi
+ commit="$y"
+ else
+ all="$all $y"
+ fi
+ done
+ echo "$commit $all"
+}
+
+# 54
+test_expect_success 'verify one file change per commit' '
+ x= &&
+ list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' &&
+# test_debug "echo HERE" &&
+# test_debug "echo ''"$list"''" &&
+ (git log --pretty=format:'"'commit: %H'"' | joincommits |
+ ( while read commit a b; do
+ test_debug "echo Verifying commit "''"$commit"''
+ test_debug "echo a: "''"$a"''
+ test_debug "echo b: "''"$b"''
+ check_equal "$b" ""
+ x=1
+ done
+ check_equal "$x" 1
+ ))
+'
+
+test_done
--- /dev/null
+
+ delete tempdir
+
+ 'git subtree rejoin' option to do the same as --rejoin, eg. after a
+ rebase
+
+ --prefix doesn't force the subtree correctly in merge/pull:
+ "-s subtree" should be given an explicit subtree option?
+ There doesn't seem to be a way to do this. We'd have to
+ patch git-merge-subtree. Ugh.
+ (but we could avoid this problem by generating squashes with
+ exactly the right subtree structure, rather than using
+ subtree merge...)
+
+ add a 'push' subcommand to parallel 'pull'
+
+ add a 'log' subcommand to see what's new in a subtree?
+
+ add to-submodule and from-submodule commands
+
+ automated tests for --squash stuff
+
+ "add" command non-obviously requires a commitid; would be easier if
+ it had a "pull" sort of mode instead
+
+ "pull" and "merge" commands should fail if you've never merged
+ that --prefix before
+
+ docs should provide an example of "add"
+
+ note that the initial split doesn't *have* to have a commitid
+ specified... that's just an optimization
+
+ if you try to add (or maybe merge?) with an invalid commitid, you
+ get a misleading "prefix must end with /" message from
+ one of the other git tools that git-subtree calls. Should
+ detect this situation and print the *real* problem.
+
+ "pull --squash" should do fetch-synthesize-merge, but instead just
+ does "pull" directly, which doesn't work at all.
+
+ make a 'force-update' that does what 'add' does even if the subtree
+ already exists. That way we can help people who imported
+ subtrees "incorrectly" (eg. by just copying in the files) in
+ the past.
+
+ guess --prefix automatically if possible based on pwd
+
+ make a 'git subtree grafts' that automatically expands --squash'd
+ commits so you can see the full history if you want it.
return offset * eastwest;
}
-const char *show_date_relative(unsigned long time, int tz,
+void show_date_relative(unsigned long time, int tz,
const struct timeval *now,
- char *timebuf,
- size_t timebuf_size)
+ struct strbuf *timebuf)
{
unsigned long diff;
- if (now->tv_sec < time)
- return "in the future";
+ if (now->tv_sec < time) {
+ strbuf_addstr(timebuf, _("in the future"));
+ return;
+ }
diff = now->tv_sec - time;
if (diff < 90) {
- snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu second ago", "%lu seconds ago", diff), diff);
+ return;
}
/* Turn it into minutes */
diff = (diff + 30) / 60;
if (diff < 90) {
- snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu minute ago", "%lu minutes ago", diff), diff);
+ return;
}
/* Turn it into hours */
diff = (diff + 30) / 60;
if (diff < 36) {
- snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu hour ago", "%lu hours ago", diff), diff);
+ return;
}
/* We deal with number of days from here on */
diff = (diff + 12) / 24;
if (diff < 14) {
- snprintf(timebuf, timebuf_size, "%lu days ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu day ago", "%lu days ago", diff), diff);
+ return;
}
/* Say weeks for the past 10 weeks or so */
if (diff < 70) {
- snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu week ago", "%lu weeks ago", (diff + 3) / 7),
+ (diff + 3) / 7);
+ return;
}
/* Say months for the past 12 months or so */
if (diff < 365) {
- snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu month ago", "%lu months ago", (diff + 15) / 30),
+ (diff + 15) / 30);
+ return;
}
/* Give years and months for 5 years or so */
if (diff < 1825) {
unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
unsigned long years = totalmonths / 12;
unsigned long months = totalmonths % 12;
- int n;
- n = snprintf(timebuf, timebuf_size, "%lu year%s",
- years, (years > 1 ? "s" : ""));
- if (months)
- snprintf(timebuf + n, timebuf_size - n,
- ", %lu month%s ago",
- months, (months > 1 ? "s" : ""));
- else
- snprintf(timebuf + n, timebuf_size - n, " ago");
- return timebuf;
+ if (months) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addf(&sb, Q_("%lu year", "%lu years", years), years);
+ /* TRANSLATORS: "%s" is "<n> years" */
+ strbuf_addf(timebuf,
+ Q_("%s, %lu month ago", "%s, %lu months ago", months),
+ sb.buf, months);
+ strbuf_release(&sb);
+ } else
+ strbuf_addf(timebuf,
+ Q_("%lu year ago", "%lu years ago", years), years);
+ return;
}
/* Otherwise, just years. Centuries is probably overkill. */
- snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu year ago", "%lu years ago", (diff + 183) / 365),
+ (diff + 183) / 365);
}
const char *show_date(unsigned long time, int tz, enum date_mode mode)
{
struct tm *tm;
- static char timebuf[200];
+ static struct strbuf timebuf = STRBUF_INIT;
if (mode == DATE_RAW) {
- snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
- return timebuf;
+ strbuf_reset(&timebuf);
+ strbuf_addf(&timebuf, "%lu %+05d", time, tz);
+ return timebuf.buf;
}
if (mode == DATE_RELATIVE) {
struct timeval now;
+
+ strbuf_reset(&timebuf);
gettimeofday(&now, NULL);
- return show_date_relative(time, tz, &now,
- timebuf, sizeof(timebuf));
+ show_date_relative(time, tz, &now, &timebuf);
+ return timebuf.buf;
}
if (mode == DATE_LOCAL)
tm = time_to_tm(time, tz);
if (!tm)
return NULL;
+
+ strbuf_reset(&timebuf);
if (mode == DATE_SHORT)
- sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
+ strbuf_addf(&timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday);
else if (mode == DATE_ISO8601)
- sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+ strbuf_addf(&timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
tz);
else if (mode == DATE_RFC2822)
- sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+ strbuf_addf(&timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
weekday_names[tm->tm_wday], tm->tm_mday,
month_names[tm->tm_mon], tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
else
- sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
+ strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
weekday_names[tm->tm_wday],
month_names[tm->tm_mon],
tm->tm_mday,
tm->tm_year + 1900,
(mode == DATE_LOCAL) ? 0 : ' ',
tz);
- return timebuf;
+ return timebuf.buf;
}
/*
{
int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0;
- int total_files = data->nr;
- int width, name_width, graph_width, number_width = 4, count;
+ int total_files = data->nr, count;
+ int width, name_width, graph_width, number_width = 0, bin_width = 0;
const char *reset, *add_c, *del_c;
const char *line_prefix = "";
int extra_shown = 0;
if (max_len < len)
max_len = len;
- if (file->is_binary || file->is_unmerged)
+ if (file->is_unmerged) {
+ /* "Unmerged" is 8 characters */
+ bin_width = bin_width < 8 ? 8 : bin_width;
continue;
+ }
+ if (file->is_binary) {
+ /* "Bin XXX -> YYY bytes" */
+ int w = 14 + decimal_width(file->added)
+ + decimal_width(file->deleted);
+ bin_width = bin_width < w ? w : bin_width;
+ /* Display change counts aligned with "Bin" */
+ number_width = 3;
+ continue;
+ }
+
if (max_change < change)
max_change = change;
}
* stat_name_width fixes the maximum width of the filename,
* and is also used to divide available columns if there
* aren't enough.
+ *
+ * Binary files are displayed with "Bin XXX -> YYY bytes"
+ * instead of the change count and graph. This part is treated
+ * similarly to the graph part, except that it is not
+ * "scaled". If total width is too small to accomodate the
+ * guaranteed minimum width of the filename part and the
+ * separators and this message, this message will "overflow"
+ * making the line longer than the maximum width.
*/
if (options->stat_width == -1)
width = term_columns() - options->output_prefix_length;
else
width = options->stat_width ? options->stat_width : 80;
+ number_width = decimal_width(max_change) > number_width ?
+ decimal_width(max_change) : number_width;
if (options->stat_graph_width == -1)
options->stat_graph_width = diff_stat_graph_width;
/*
* First assign sizes that are wanted, ignoring available width.
+ * strlen("Bin XXX -> YYY bytes") == bin_width, and the part
+ * starting from "XXX" should fit in graph_width.
*/
- graph_width = (options->stat_graph_width &&
- options->stat_graph_width < max_change) ?
- options->stat_graph_width : max_change;
+ graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4;
+ if (options->stat_graph_width &&
+ options->stat_graph_width < graph_width)
+ graph_width = options->stat_graph_width;
+
name_width = (options->stat_name_width > 0 &&
options->stat_name_width < max_len) ?
options->stat_name_width : max_len;
if (data->files[i]->is_binary) {
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, " Bin ");
- fprintf(options->file, "%s%"PRIuMAX"%s",
+ fprintf(options->file, " %*s", number_width, "Bin");
+ if (!added && !deleted) {
+ putc('\n', options->file);
+ continue;
+ }
+ fprintf(options->file, " %s%"PRIuMAX"%s",
del_c, deleted, reset);
fprintf(options->file, " -> ");
fprintf(options->file, "%s%"PRIuMAX"%s",
else if (data->files[i]->is_unmerged) {
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, " Unmerged\n");
+ fprintf(options->file, " Unmerged\n");
continue;
}
}
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
- added + deleted ? " " : "");
+ fprintf(options->file, " %*"PRIuMAX"%s",
+ number_width, added + deleted,
+ added + deleted ? " " : "");
show_graph(options->file, '+', add, add_c, reset);
show_graph(options->file, '-', del, del_c, reset);
fprintf(options->file, "\n");
return;
for (i = 0; i < data->nr; i++) {
- if (!data->files[i]->is_binary &&
- !data->files[i]->is_unmerged) {
- int added = data->files[i]->added;
- int deleted= data->files[i]->deleted;
- if (!data->files[i]->is_renamed &&
- (added + deleted == 0)) {
- total_files--;
- } else {
- adds += added;
- dels += deleted;
- }
+ int added = data->files[i]->added;
+ int deleted= data->files[i]->deleted;
+
+ if (data->files[i]->is_unmerged)
+ continue;
+ if (!data->files[i]->is_renamed && (added + deleted == 0)) {
+ total_files--;
+ } else {
+ adds += added;
+ dels += deleted;
}
}
if (options->output_prefix) {
{
mmfile_t mf1, mf2;
struct diffstat_file *data;
+ int same_contents;
data = diffstat_add(diffstat, name_a, name_b);
return;
}
+ same_contents = !hashcmp(one->sha1, two->sha1);
+
if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
data->is_binary = 1;
- data->added = diff_filespec_size(two);
- data->deleted = diff_filespec_size(one);
+ if (same_contents) {
+ data->added = 0;
+ data->deleted = 0;
+ } else {
+ data->added = diff_filespec_size(two);
+ data->deleted = diff_filespec_size(one);
+ }
}
else if (complete_rewrite) {
data->added = count_lines(two->data, two->size);
}
- else {
+ else if (!same_contents) {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
};
static enum path_treatment treat_one_path(struct dir_struct *dir,
- char *path, int *len,
+ struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
{
- int exclude = excluded(dir, path, &dtype);
+ int exclude = excluded(dir, path->buf, &dtype);
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
- && exclude_matches_pathspec(path, *len, simplify))
- dir_add_ignored(dir, path, *len);
+ && exclude_matches_pathspec(path->buf, path->len, simplify))
+ dir_add_ignored(dir, path->buf, path->len);
/*
* Excluded? If we don't explicitly want to show
return path_ignored;
if (dtype == DT_UNKNOWN)
- dtype = get_dtype(de, path, *len);
+ dtype = get_dtype(de, path->buf, path->len);
/*
* Do we want to see just the ignored files?
default:
return path_ignored;
case DT_DIR:
- memcpy(path + *len, "/", 2);
- (*len)++;
- switch (treat_directory(dir, path, *len, simplify)) {
+ strbuf_addch(path, '/');
+ switch (treat_directory(dir, path->buf, path->len, simplify)) {
case show_directory:
if (exclude != !!(dir->flags
& DIR_SHOW_IGNORED))
static enum path_treatment treat_path(struct dir_struct *dir,
struct dirent *de,
- char *path, int path_max,
+ struct strbuf *path,
int baselen,
- const struct path_simplify *simplify,
- int *len)
+ const struct path_simplify *simplify)
{
int dtype;
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
return path_ignored;
- *len = strlen(de->d_name);
- /* Ignore overly long pathnames! */
- if (*len + baselen + 8 > path_max)
- return path_ignored;
- memcpy(path + baselen, de->d_name, *len + 1);
- *len += baselen;
- if (simplify_away(path, *len, simplify))
+ strbuf_setlen(path, baselen);
+ strbuf_addstr(path, de->d_name);
+ if (simplify_away(path->buf, path->len, simplify))
return path_ignored;
dtype = DTYPE(de);
- return treat_one_path(dir, path, len, simplify, dtype, de);
+ return treat_one_path(dir, path, simplify, dtype, de);
}
/*
DIR *fdir = opendir(*base ? base : ".");
int contents = 0;
struct dirent *de;
- char path[PATH_MAX + 1];
+ struct strbuf path = STRBUF_INIT;
if (!fdir)
return 0;
- memcpy(path, base, baselen);
+ strbuf_add(&path, base, baselen);
while ((de = readdir(fdir)) != NULL) {
- int len;
- switch (treat_path(dir, de, path, sizeof(path),
- baselen, simplify, &len)) {
+ switch (treat_path(dir, de, &path, baselen, simplify)) {
case path_recurse:
- contents += read_directory_recursive(dir, path, len, 0, simplify);
+ contents += read_directory_recursive(dir, path.buf,
+ path.len, 0,
+ simplify);
continue;
case path_ignored:
continue;
if (check_only)
goto exit_early;
else
- dir_add_name(dir, path, len);
+ dir_add_name(dir, path.buf, path.len);
}
exit_early:
closedir(fdir);
+ strbuf_release(&path);
return contents;
}
const char *path, int len,
const struct path_simplify *simplify)
{
- char pathbuf[PATH_MAX];
- int baselen, blen;
+ struct strbuf sb = STRBUF_INIT;
+ int baselen, rc = 0;
const char *cp;
while (len && path[len - 1] == '/')
baselen = len;
else
baselen = cp - path;
- memcpy(pathbuf, path, baselen);
- pathbuf[baselen] = '\0';
- if (!is_directory(pathbuf))
- return 0;
- if (simplify_away(pathbuf, baselen, simplify))
- return 0;
- blen = baselen;
- if (treat_one_path(dir, pathbuf, &blen, simplify,
+ strbuf_setlen(&sb, 0);
+ strbuf_add(&sb, path, baselen);
+ if (!is_directory(sb.buf))
+ break;
+ if (simplify_away(sb.buf, sb.len, simplify))
+ break;
+ if (treat_one_path(dir, &sb, simplify,
DT_DIR, NULL) == path_ignored)
- return 0; /* do not recurse into it */
- if (len <= baselen)
- return 1; /* finished checking */
+ break; /* do not recurse into it */
+ if (len <= baselen) {
+ rc = 1;
+ break; /* finished checking */
+ }
}
+ strbuf_release(&sb);
+ return rc;
}
int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
const struct checkout *state, int to_tempfile,
int *fstat_done, struct stat *statbuf)
{
- struct git_istream *st;
- enum object_type type;
- unsigned long sz;
int result = -1;
- ssize_t kept = 0;
- int fd = -1;
-
- st = open_istream(ce->sha1, &type, &sz, filter);
- if (!st)
- return -1;
- if (type != OBJ_BLOB)
- goto close_and_exit;
+ int fd;
fd = open_output_fd(path, ce, to_tempfile);
- if (fd < 0)
- goto close_and_exit;
-
- for (;;) {
- char buf[1024 * 16];
- ssize_t wrote, holeto;
- ssize_t readlen = read_istream(st, buf, sizeof(buf));
-
- if (!readlen)
- break;
- if (sizeof(buf) == readlen) {
- for (holeto = 0; holeto < readlen; holeto++)
- if (buf[holeto])
- break;
- if (readlen == holeto) {
- kept += holeto;
- continue;
- }
- }
-
- if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
- goto close_and_exit;
- else
- kept = 0;
- wrote = write_in_full(fd, buf, readlen);
-
- if (wrote != readlen)
- goto close_and_exit;
- }
- if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
- write(fd, "", 1) != 1))
- goto close_and_exit;
- *fstat_done = fstat_output(fd, state, statbuf);
-
-close_and_exit:
- close_istream(st);
- if (0 <= fd)
+ if (0 <= fd) {
+ result = stream_blob_to_fd(fd, ce->sha1, filter, 1);
+ *fstat_done = fstat_output(fd, state, statbuf);
result = close(fd);
+ }
if (result && 0 <= fd)
unlink(path);
return result;
else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data);
else {
- result = error("in tree %s: entry %s has bad mode %.6o\n",
+ result = error("in tree %s: entry %s has bad mode %.6o",
sha1_to_hex(tree->object.sha1), entry.path, entry.mode);
}
if (result < 0)
/^NAME/,/git-'"$cmd"'/H
${
x
- s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/
+ s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", N_("\1")},/
p
}' "Documentation/git-$cmd.txt"
done
ignore-whitespace pass it through git-apply
directory= pass it through git-apply
exclude= pass it through git-apply
+include= pass it through git-apply
C= pass it through git-apply
p= pass it through git-apply
patch-format= format the patch(es) are in
say Using index info to reconstruct a base tree...
cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
+
+ if test -z "$GIT_QUIET"
+ then
+ eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
+ fi
+
cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
if eval "$cmd"
then
;;
--resolvemsg)
shift; resolvemsg=$1 ;;
- --whitespace|--directory|--exclude)
+ --whitespace|--directory|--exclude|--include)
git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
-C|-p)
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
fi
fi
-# Launch the merge tool on each path provided by 'git diff'
-while test $# -gt 6
-do
- launch_merge_tool "$1" "$2" "$5"
- shift 7
-done
+if test -n "$GIT_DIFFTOOL_DIRDIFF"
+then
+ LOCAL="$1"
+ REMOTE="$2"
+ run_merge_tool "$merge_tool" false
+else
+ # Launch the merge tool on each path provided by 'git diff'
+ while test $# -gt 6
+ do
+ launch_merge_tool "$1" "$2" "$5"
+ shift 7
+ done
+fi
-#!/usr/bin/env perl
+#!/usr/bin/perl
# Copyright (c) 2009, 2010 David Aguilar
+# Copyright (c) 2012 Tim Henigan
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool--helper script.
#
# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
-# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
-# are exported for use by git-difftool--helper.
+# The GIT_DIFF* variables are exported for use by git-difftool--helper.
#
# Any arguments that are unknown to this script are forwarded to 'git diff'.
use 5.008;
use strict;
use warnings;
-use Cwd qw(abs_path);
use File::Basename qw(dirname);
+use File::Copy;
+use File::Find;
+use File::stat;
+use File::Path qw(mkpath);
+use File::Temp qw(tempdir);
+use Getopt::Long qw(:config pass_through);
+use Git;
-require Git;
-
-my $DIR = abs_path(dirname($0));
-
+my @tools;
+my @working_tree;
+my $rc;
+my $repo = Git->repository();
+my $repo_path = $repo->repo_path();
sub usage
{
+ my $exitcode = shift;
print << 'USAGE';
-usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
- [-y|--no-prompt] [-g|--gui]
+usage: git difftool [-t|--tool=<tool>] [--tool-help]
+ [-x|--extcmd=<cmd>]
+ [-g|--gui] [--no-gui]
+ [--prompt] [-y|--no-prompt]
+ [-d|--dir-diff]
['git diff' options]
USAGE
- exit 1;
+ exit($exitcode);
}
-sub setup_environment
+sub find_worktree
{
- $ENV{PATH} = "$DIR:$ENV{PATH}";
- $ENV{GIT_PAGER} = '';
- $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+ # Git->repository->wc_path() does not honor changes to the working
+ # tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'
+ # config variable.
+ my $worktree;
+ my $env_worktree = $ENV{GIT_WORK_TREE};
+ my $core_worktree = Git::config('core.worktree');
+
+ if (defined($env_worktree) and (length($env_worktree) > 0)) {
+ $worktree = $env_worktree;
+ } elsif (defined($core_worktree) and (length($core_worktree) > 0)) {
+ $worktree = $core_worktree;
+ } else {
+ $worktree = $repo->wc_path();
+ }
+
+ return $worktree;
}
-sub exe
+my $workdir = find_worktree();
+
+sub filter_tool_scripts
{
- my $exe = shift;
- if ($^O eq 'MSWin32' || $^O eq 'msys') {
- return "$exe.exe";
+ if (-d $_) {
+ if ($_ ne ".") {
+ # Ignore files in subdirectories
+ $File::Find::prune = 1;
+ }
+ } else {
+ if ((-f $_) && ($_ ne "defaults")) {
+ push(@tools, $_);
+ }
}
- return $exe;
}
-sub generate_command
+sub print_tool_help
{
- my @command = (exe('git'), 'diff');
- my $skip_next = 0;
- my $idx = -1;
- my $prompt = '';
- for my $arg (@ARGV) {
- $idx++;
- if ($skip_next) {
- $skip_next = 0;
- next;
+ my ($cmd, @found, @notfound);
+ my $gitpath = Git::exec_path();
+
+ find(\&filter_tool_scripts, "$gitpath/mergetools");
+
+ foreach my $tool (@tools) {
+ $cmd = "TOOL_MODE=diff";
+ $cmd .= ' && . "$(git --exec-path)/git-mergetool--lib"';
+ $cmd .= " && get_merge_tool_path $tool >/dev/null 2>&1";
+ $cmd .= " && can_diff >/dev/null 2>&1";
+ if (system('sh', '-c', $cmd) == 0) {
+ push(@found, $tool);
+ } else {
+ push(@notfound, $tool);
}
- if ($arg eq '-t' || $arg eq '--tool') {
- usage() if $#ARGV <= $idx;
- $ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
- $skip_next = 1;
- next;
+ }
+
+ print "'git difftool --tool=<tool>' may be set to one of the following:\n";
+ print "\t$_\n" for (sort(@found));
+
+ print "\nThe following tools are valid, but not currently available:\n";
+ print "\t$_\n" for (sort(@notfound));
+
+ print "\nNOTE: Some of the tools listed above only work in a windowed\n";
+ print "environment. If run in a terminal-only session, they will fail.\n";
+
+ exit(0);
+}
+
+sub setup_dir_diff
+{
+ # Run the diff; exit immediately if no diff found
+ # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
+ # if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used
+ # by Git->repository->command*.
+ my $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);
+ my $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);
+ exit(0) if (length($diffrtn) == 0);
+
+ # Setup temp directories
+ my $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);
+ my $ldir = "$tmpdir/left";
+ my $rdir = "$tmpdir/right";
+ mkpath($ldir) or die $!;
+ mkpath($rdir) or die $!;
+
+ # Build index info for left and right sides of the diff
+ my $submodule_mode = '160000';
+ my $symlink_mode = '120000';
+ my $null_mode = '0' x 6;
+ my $null_sha1 = '0' x 40;
+ my $lindex = '';
+ my $rindex = '';
+ my %submodule;
+ my %symlink;
+ my @rawdiff = split('\0', $diffrtn);
+
+ my $i = 0;
+ while ($i < $#rawdiff) {
+ if ($rawdiff[$i] =~ /^::/) {
+ print "Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\n";
+ exit(1);
}
- if ($arg =~ /^--tool=/) {
- $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
- next;
+
+ my ($lmode, $rmode, $lsha1, $rsha1, $status) = split(' ', substr($rawdiff[$i], 1));
+ my $src_path = $rawdiff[$i + 1];
+ my $dst_path;
+
+ if ($status =~ /^[CR]/) {
+ $dst_path = $rawdiff[$i + 2];
+ $i += 3;
+ } else {
+ $dst_path = $src_path;
+ $i += 2;
}
- if ($arg eq '-x' || $arg eq '--extcmd') {
- usage() if $#ARGV <= $idx;
- $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
- $skip_next = 1;
+
+ if (($lmode eq $submodule_mode) or ($rmode eq $submodule_mode)) {
+ $submodule{$src_path}{left} = $lsha1;
+ if ($lsha1 ne $rsha1) {
+ $submodule{$dst_path}{right} = $rsha1;
+ } else {
+ $submodule{$dst_path}{right} = "$rsha1-dirty";
+ }
next;
}
- if ($arg =~ /^--extcmd=/) {
- $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
- next;
+
+ if ($lmode eq $symlink_mode) {
+ $symlink{$src_path}{left} = $diffrepo->command_oneline('show', "$lsha1");
}
- if ($arg eq '-g' || $arg eq '--gui') {
- eval {
- my $tool = Git::command_oneline('config',
- 'diff.guitool');
- if (length($tool)) {
- $ENV{GIT_DIFF_TOOL} = $tool;
- }
- };
- next;
+
+ if ($rmode eq $symlink_mode) {
+ $symlink{$dst_path}{right} = $diffrepo->command_oneline('show', "$rsha1");
}
- if ($arg eq '-y' || $arg eq '--no-prompt') {
- $prompt = 'no';
- next;
+
+ if (($lmode ne $null_mode) and ($status !~ /^C/)) {
+ $lindex .= "$lmode $lsha1\t$src_path\0";
}
- if ($arg eq '--prompt') {
- $prompt = 'yes';
- next;
+
+ if ($rmode ne $null_mode) {
+ if ($rsha1 ne $null_sha1) {
+ $rindex .= "$rmode $rsha1\t$dst_path\0";
+ } else {
+ push(@working_tree, $dst_path);
+ }
}
- if ($arg eq '-h') {
- usage();
+ }
+
+ # If $GIT_DIR is not set prior to calling 'git update-index' and
+ # 'git checkout-index', then those commands will fail if difftool
+ # is called from a directory other than the repo root.
+ my $must_unset_git_dir = 0;
+ if (not defined($ENV{GIT_DIR})) {
+ $must_unset_git_dir = 1;
+ $ENV{GIT_DIR} = $repo_path;
+ }
+
+ # Populate the left and right directories based on each index file
+ my ($inpipe, $ctx);
+ $ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
+ ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ print($inpipe $lindex);
+ $repo->command_close_pipe($inpipe, $ctx);
+ $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
+ ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ print($inpipe $rindex);
+ $repo->command_close_pipe($inpipe, $ctx);
+ $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ # If $GIT_DIR was explicitly set just for the update/checkout
+ # commands, then it should be unset before continuing.
+ delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
+ delete($ENV{GIT_INDEX_FILE});
+
+ # Changes in the working tree need special treatment since they are
+ # not part of the index
+ for my $file (@working_tree) {
+ my $dir = dirname($file);
+ unless (-d "$rdir/$dir") {
+ mkpath("$rdir/$dir") or die $!;
+ }
+ copy("$workdir/$file", "$rdir/$file") or die $!;
+ chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!;
+ }
+
+ # Changes to submodules require special treatment. This loop writes a
+ # temporary file to both the left and right directories to show the
+ # change in the recorded SHA1 for the submodule.
+ for my $path (keys %submodule) {
+ if (defined($submodule{$path}{left})) {
+ write_to_file("$ldir/$path", "Subproject commit $submodule{$path}{left}");
+ }
+ if (defined($submodule{$path}{right})) {
+ write_to_file("$rdir/$path", "Subproject commit $submodule{$path}{right}");
+ }
+ }
+
+ # Symbolic links require special treatment. The standard "git diff"
+ # shows only the link itself, not the contents of the link target.
+ # This loop replicates that behavior.
+ for my $path (keys %symlink) {
+ if (defined($symlink{$path}{left})) {
+ write_to_file("$ldir/$path", $symlink{$path}{left});
+ }
+ if (defined($symlink{$path}{right})) {
+ write_to_file("$rdir/$path", $symlink{$path}{right});
}
- push @command, $arg;
}
- if ($prompt eq 'yes') {
- $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
- } elsif ($prompt eq 'no') {
- $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+
+ return ($ldir, $rdir);
+}
+
+sub write_to_file
+{
+ my $path = shift;
+ my $value = shift;
+
+ # Make sure the path to the file exists
+ my $dir = dirname($path);
+ unless (-d "$dir") {
+ mkpath("$dir") or die $!;
}
- return @command
+
+ # If the file already exists in that location, delete it. This
+ # is required in the case of symbolic links.
+ unlink("$path");
+
+ open(my $fh, '>', "$path") or die $!;
+ print($fh $value);
+ close($fh);
}
-setup_environment();
+# parse command-line options. all unrecognized options and arguments
+# are passed through to the 'git diff' command.
+my ($difftool_cmd, $dirdiff, $extcmd, $gui, $help, $prompt, $tool_help);
+GetOptions('g|gui!' => \$gui,
+ 'd|dir-diff' => \$dirdiff,
+ 'h' => \$help,
+ 'prompt!' => \$prompt,
+ 'y' => sub { $prompt = 0; },
+ 't|tool:s' => \$difftool_cmd,
+ 'tool-help' => \$tool_help,
+ 'x|extcmd:s' => \$extcmd);
-# ActiveState Perl for Win32 does not implement POSIX semantics of
-# exec* system call. It just spawns the given executable and finishes
-# the starting program, exiting with code 0.
-# system will at least catch the errors returned by git diff,
-# allowing the caller of git difftool better handling of failures.
-my $rc = system(generate_command());
-exit($rc | ($rc >> 8));
+if (defined($help)) {
+ usage(0);
+}
+if (defined($tool_help)) {
+ print_tool_help();
+}
+if (defined($difftool_cmd)) {
+ if (length($difftool_cmd) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $difftool_cmd;
+ } else {
+ print "No <tool> given for --tool=<tool>\n";
+ usage(1);
+ }
+}
+if (defined($extcmd)) {
+ if (length($extcmd) > 0) {
+ $ENV{GIT_DIFFTOOL_EXTCMD} = $extcmd;
+ } else {
+ print "No <cmd> given for --extcmd=<cmd>\n";
+ usage(1);
+ }
+}
+if ($gui) {
+ my $guitool = '';
+ $guitool = Git::config('diff.guitool');
+ if (length($guitool) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $guitool;
+ }
+}
+
+# In directory diff mode, 'git-difftool--helper' is called once
+# to compare the a/b directories. In file diff mode, 'git diff'
+# will invoke a separate instance of 'git-difftool--helper' for
+# each file that changed.
+if (defined($dirdiff)) {
+ my ($a, $b) = setup_dir_diff();
+ if (defined($extcmd)) {
+ $rc = system($extcmd, $a, $b);
+ } else {
+ $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
+ $rc = system('git', 'difftool--helper', $a, $b);
+ }
+
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ # If the diff including working copy files and those
+ # files were modified during the diff, then the changes
+ # should be copied back to the working tree
+ for my $file (@working_tree) {
+ copy("$b/$file", "$workdir/$file") or die $!;
+ chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
+ }
+} else {
+ if (defined($prompt)) {
+ if ($prompt) {
+ $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+ } else {
+ $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+ }
+ }
+
+ $ENV{GIT_PAGER} = '';
+ $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+
+ # ActiveState Perl for Win32 does not implement POSIX semantics of
+ # exec* system call. It just spawns the given executable and finishes
+ # the starting program, exiting with code 0.
+ # system will at least catch the errors returned by git diff,
+ # allowing the caller of git difftool better handling of failures.
+ my $rc = system('git', 'diff', @ARGV);
+ exit($rc | ($rc >> 8));
+}
--- /dev/null
+#!/usr/bin/env python
+#
+# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
+#
+# Author: Simon Hausmann <simon@lst.de>
+# Copyright: 2007 Simon Hausmann <simon@lst.de>
+# 2007 Trolltech ASA
+# License: MIT <http://www.opensource.org/licenses/mit-license.php>
+#
+
+import optparse, sys, os, marshal, subprocess, shelve
+import tempfile, getopt, os.path, time, platform
+import re, shutil
+
+verbose = False
+
+# Only labels/tags matching this will be imported/exported
+defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
+
+def p4_build_cmd(cmd):
+ """Build a suitable p4 command line.
+
+ This consolidates building and returning a p4 command line into one
+ location. It means that hooking into the environment, or other configuration
+ can be done more easily.
+ """
+ real_cmd = ["p4"]
+
+ user = gitConfig("git-p4.user")
+ if len(user) > 0:
+ real_cmd += ["-u",user]
+
+ password = gitConfig("git-p4.password")
+ if len(password) > 0:
+ real_cmd += ["-P", password]
+
+ port = gitConfig("git-p4.port")
+ if len(port) > 0:
+ real_cmd += ["-p", port]
+
+ host = gitConfig("git-p4.host")
+ if len(host) > 0:
+ real_cmd += ["-H", host]
+
+ client = gitConfig("git-p4.client")
+ if len(client) > 0:
+ real_cmd += ["-c", client]
+
+
+ if isinstance(cmd,basestring):
+ real_cmd = ' '.join(real_cmd) + ' ' + cmd
+ else:
+ real_cmd += cmd
+ return real_cmd
+
+def chdir(dir):
+ # P4 uses the PWD environment variable rather than getcwd(). Since we're
+ # not using the shell, we have to set it ourselves. This path could
+ # be relative, so go there first, then figure out where we ended up.
+ os.chdir(dir)
+ os.environ['PWD'] = os.getcwd()
+
+def die(msg):
+ if verbose:
+ raise Exception(msg)
+ else:
+ sys.stderr.write(msg + "\n")
+ sys.exit(1)
+
+def write_pipe(c, stdin):
+ if verbose:
+ sys.stderr.write('Writing pipe: %s\n' % str(c))
+
+ expand = isinstance(c,basestring)
+ p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+ pipe = p.stdin
+ val = pipe.write(stdin)
+ pipe.close()
+ if p.wait():
+ die('Command failed: %s' % str(c))
+
+ return val
+
+def p4_write_pipe(c, stdin):
+ real_cmd = p4_build_cmd(c)
+ return write_pipe(real_cmd, stdin)
+
+def read_pipe(c, ignore_error=False):
+ if verbose:
+ sys.stderr.write('Reading pipe: %s\n' % str(c))
+
+ expand = isinstance(c,basestring)
+ p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+ pipe = p.stdout
+ val = pipe.read()
+ if p.wait() and not ignore_error:
+ die('Command failed: %s' % str(c))
+
+ return val
+
+def p4_read_pipe(c, ignore_error=False):
+ real_cmd = p4_build_cmd(c)
+ return read_pipe(real_cmd, ignore_error)
+
+def read_pipe_lines(c):
+ if verbose:
+ sys.stderr.write('Reading pipe: %s\n' % str(c))
+
+ expand = isinstance(c, basestring)
+ p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+ pipe = p.stdout
+ val = pipe.readlines()
+ if pipe.close() or p.wait():
+ die('Command failed: %s' % str(c))
+
+ return val
+
+def p4_read_pipe_lines(c):
+ """Specifically invoke p4 on the command supplied. """
+ real_cmd = p4_build_cmd(c)
+ return read_pipe_lines(real_cmd)
+
+def system(cmd):
+ expand = isinstance(cmd,basestring)
+ if verbose:
+ sys.stderr.write("executing %s\n" % str(cmd))
+ subprocess.check_call(cmd, shell=expand)
+
+def p4_system(cmd):
+ """Specifically invoke p4 as the system command. """
+ real_cmd = p4_build_cmd(cmd)
+ expand = isinstance(real_cmd, basestring)
+ subprocess.check_call(real_cmd, shell=expand)
+
+def p4_integrate(src, dest):
+ p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
+
+def p4_sync(f, *options):
+ p4_system(["sync"] + list(options) + [wildcard_encode(f)])
+
+def p4_add(f):
+ # forcibly add file names with wildcards
+ if wildcard_present(f):
+ p4_system(["add", "-f", f])
+ else:
+ p4_system(["add", f])
+
+def p4_delete(f):
+ p4_system(["delete", wildcard_encode(f)])
+
+def p4_edit(f):
+ p4_system(["edit", wildcard_encode(f)])
+
+def p4_revert(f):
+ p4_system(["revert", wildcard_encode(f)])
+
+def p4_reopen(type, f):
+ p4_system(["reopen", "-t", type, wildcard_encode(f)])
+
+#
+# Canonicalize the p4 type and return a tuple of the
+# base type, plus any modifiers. See "p4 help filetypes"
+# for a list and explanation.
+#
+def split_p4_type(p4type):
+
+ p4_filetypes_historical = {
+ "ctempobj": "binary+Sw",
+ "ctext": "text+C",
+ "cxtext": "text+Cx",
+ "ktext": "text+k",
+ "kxtext": "text+kx",
+ "ltext": "text+F",
+ "tempobj": "binary+FSw",
+ "ubinary": "binary+F",
+ "uresource": "resource+F",
+ "uxbinary": "binary+Fx",
+ "xbinary": "binary+x",
+ "xltext": "text+Fx",
+ "xtempobj": "binary+Swx",
+ "xtext": "text+x",
+ "xunicode": "unicode+x",
+ "xutf16": "utf16+x",
+ }
+ if p4type in p4_filetypes_historical:
+ p4type = p4_filetypes_historical[p4type]
+ mods = ""
+ s = p4type.split("+")
+ base = s[0]
+ mods = ""
+ if len(s) > 1:
+ mods = s[1]
+ return (base, mods)
+
+#
+# return the raw p4 type of a file (text, text+ko, etc)
+#
+def p4_type(file):
+ results = p4CmdList(["fstat", "-T", "headType", file])
+ return results[0]['headType']
+
+#
+# Given a type base and modifier, return a regexp matching
+# the keywords that can be expanded in the file
+#
+def p4_keywords_regexp_for_type(base, type_mods):
+ if base in ("text", "unicode", "binary"):
+ kwords = None
+ if "ko" in type_mods:
+ kwords = 'Id|Header'
+ elif "k" in type_mods:
+ kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
+ else:
+ return None
+ pattern = r"""
+ \$ # Starts with a dollar, followed by...
+ (%s) # one of the keywords, followed by...
+ (:[^$]+)? # possibly an old expansion, followed by...
+ \$ # another dollar
+ """ % kwords
+ return pattern
+ else:
+ return None
+
+#
+# Given a file, return a regexp matching the possible
+# RCS keywords that will be expanded, or None for files
+# with kw expansion turned off.
+#
+def p4_keywords_regexp_for_file(file):
+ if not os.path.exists(file):
+ return None
+ else:
+ (type_base, type_mods) = split_p4_type(p4_type(file))
+ return p4_keywords_regexp_for_type(type_base, type_mods)
+
+def setP4ExecBit(file, mode):
+ # Reopens an already open file and changes the execute bit to match
+ # the execute bit setting in the passed in mode.
+
+ p4Type = "+x"
+
+ if not isModeExec(mode):
+ p4Type = getP4OpenedType(file)
+ p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
+ p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
+ if p4Type[-1] == "+":
+ p4Type = p4Type[0:-1]
+
+ p4_reopen(p4Type, file)
+
+def getP4OpenedType(file):
+ # Returns the perforce file type for the given file.
+
+ result = p4_read_pipe(["opened", wildcard_encode(file)])
+ match = re.match(".*\((.+)\)\r?$", result)
+ if match:
+ return match.group(1)
+ else:
+ die("Could not determine file type for %s (result: '%s')" % (file, result))
+
+# Return the set of all p4 labels
+def getP4Labels(depotPaths):
+ labels = set()
+ if isinstance(depotPaths,basestring):
+ depotPaths = [depotPaths]
+
+ for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
+ label = l['label']
+ labels.add(label)
+
+ return labels
+
+# Return the set of all git tags
+def getGitTags():
+ gitTags = set()
+ for line in read_pipe_lines(["git", "tag"]):
+ tag = line.strip()
+ gitTags.add(tag)
+ return gitTags
+
+def diffTreePattern():
+ # This is a simple generator for the diff tree regex pattern. This could be
+ # a class variable if this and parseDiffTreeEntry were a part of a class.
+ pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+ while True:
+ yield pattern
+
+def parseDiffTreeEntry(entry):
+ """Parses a single diff tree entry into its component elements.
+
+ See git-diff-tree(1) manpage for details about the format of the diff
+ output. This method returns a dictionary with the following elements:
+
+ src_mode - The mode of the source file
+ dst_mode - The mode of the destination file
+ src_sha1 - The sha1 for the source file
+ dst_sha1 - The sha1 fr the destination file
+ status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
+ status_score - The score for the status (applicable for 'C' and 'R'
+ statuses). This is None if there is no score.
+ src - The path for the source file.
+ dst - The path for the destination file. This is only present for
+ copy or renames. If it is not present, this is None.
+
+ If the pattern is not matched, None is returned."""
+
+ match = diffTreePattern().next().match(entry)
+ if match:
+ return {
+ 'src_mode': match.group(1),
+ 'dst_mode': match.group(2),
+ 'src_sha1': match.group(3),
+ 'dst_sha1': match.group(4),
+ 'status': match.group(5),
+ 'status_score': match.group(6),
+ 'src': match.group(7),
+ 'dst': match.group(10)
+ }
+ return None
+
+def isModeExec(mode):
+ # Returns True if the given git mode represents an executable file,
+ # otherwise False.
+ return mode[-3:] == "755"
+
+def isModeExecChanged(src_mode, dst_mode):
+ return isModeExec(src_mode) != isModeExec(dst_mode)
+
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
+
+ if isinstance(cmd,basestring):
+ cmd = "-G " + cmd
+ expand = True
+ else:
+ cmd = ["-G"] + cmd
+ expand = False
+
+ cmd = p4_build_cmd(cmd)
+ if verbose:
+ sys.stderr.write("Opening pipe: %s\n" % str(cmd))
+
+ # Use a temporary file to avoid deadlocks without
+ # subprocess.communicate(), which would put another copy
+ # of stdout into memory.
+ stdin_file = None
+ if stdin is not None:
+ stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
+ if isinstance(stdin,basestring):
+ stdin_file.write(stdin)
+ else:
+ for i in stdin:
+ stdin_file.write(i + '\n')
+ stdin_file.flush()
+ stdin_file.seek(0)
+
+ p4 = subprocess.Popen(cmd,
+ shell=expand,
+ stdin=stdin_file,
+ stdout=subprocess.PIPE)
+
+ result = []
+ try:
+ while True:
+ entry = marshal.load(p4.stdout)
+ if cb is not None:
+ cb(entry)
+ else:
+ result.append(entry)
+ except EOFError:
+ pass
+ exitCode = p4.wait()
+ if exitCode != 0:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
+
+ return result
+
+def p4Cmd(cmd):
+ list = p4CmdList(cmd)
+ result = {}
+ for entry in list:
+ result.update(entry)
+ return result;
+
+def p4Where(depotPath):
+ if not depotPath.endswith("/"):
+ depotPath += "/"
+ depotPath = depotPath + "..."
+ outputList = p4CmdList(["where", depotPath])
+ output = None
+ for entry in outputList:
+ if "depotFile" in entry:
+ if entry["depotFile"] == depotPath:
+ output = entry
+ break
+ elif "data" in entry:
+ data = entry.get("data")
+ space = data.find(" ")
+ if data[:space] == depotPath:
+ output = entry
+ break
+ if output == None:
+ return ""
+ if output["code"] == "error":
+ return ""
+ clientPath = ""
+ if "path" in output:
+ clientPath = output.get("path")
+ elif "data" in output:
+ data = output.get("data")
+ lastSpace = data.rfind(" ")
+ clientPath = data[lastSpace + 1:]
+
+ if clientPath.endswith("..."):
+ clientPath = clientPath[:-3]
+ return clientPath
+
+def currentGitBranch():
+ return read_pipe("git name-rev HEAD").split(" ")[1].strip()
+
+def isValidGitDir(path):
+ if (os.path.exists(path + "/HEAD")
+ and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
+ return True;
+ return False
+
+def parseRevision(ref):
+ return read_pipe("git rev-parse %s" % ref).strip()
+
+def branchExists(ref):
+ rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
+ ignore_error=True)
+ return len(rev) > 0
+
+def extractLogMessageFromGitCommit(commit):
+ logMessage = ""
+
+ ## fixme: title is first line of commit, not 1st paragraph.
+ foundTitle = False
+ for log in read_pipe_lines("git cat-file commit %s" % commit):
+ if not foundTitle:
+ if len(log) == 1:
+ foundTitle = True
+ continue
+
+ logMessage += log
+ return logMessage
+
+def extractSettingsGitLog(log):
+ values = {}
+ for line in log.split("\n"):
+ line = line.strip()
+ m = re.search (r"^ *\[git-p4: (.*)\]$", line)
+ if not m:
+ continue
+
+ assignments = m.group(1).split (':')
+ for a in assignments:
+ vals = a.split ('=')
+ key = vals[0].strip()
+ val = ('='.join (vals[1:])).strip()
+ if val.endswith ('\"') and val.startswith('"'):
+ val = val[1:-1]
+
+ values[key] = val
+
+ paths = values.get("depot-paths")
+ if not paths:
+ paths = values.get("depot-path")
+ if paths:
+ values['depot-paths'] = paths.split(',')
+ return values
+
+def gitBranchExists(branch):
+ proc = subprocess.Popen(["git", "rev-parse", branch],
+ stderr=subprocess.PIPE, stdout=subprocess.PIPE);
+ return proc.wait() == 0;
+
+_gitConfig = {}
+def gitConfig(key, args = None): # set args to "--bool", for instance
+ if not _gitConfig.has_key(key):
+ argsFilter = ""
+ if args != None:
+ argsFilter = "%s " % args
+ cmd = "git config %s%s" % (argsFilter, key)
+ _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
+ return _gitConfig[key]
+
+def gitConfigList(key):
+ if not _gitConfig.has_key(key):
+ _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
+ return _gitConfig[key]
+
+def p4BranchesInGit(branchesAreInRemotes = True):
+ branches = {}
+
+ cmdline = "git rev-parse --symbolic "
+ if branchesAreInRemotes:
+ cmdline += " --remotes"
+ else:
+ cmdline += " --branches"
+
+ for line in read_pipe_lines(cmdline):
+ line = line.strip()
+
+ ## only import to p4/
+ if not line.startswith('p4/') or line == "p4/HEAD":
+ continue
+ branch = line
+
+ # strip off p4
+ branch = re.sub ("^p4/", "", line)
+
+ branches[branch] = parseRevision(line)
+ return branches
+
+def findUpstreamBranchPoint(head = "HEAD"):
+ branches = p4BranchesInGit()
+ # map from depot-path to branch name
+ branchByDepotPath = {}
+ for branch in branches.keys():
+ tip = branches[branch]
+ log = extractLogMessageFromGitCommit(tip)
+ settings = extractSettingsGitLog(log)
+ if settings.has_key("depot-paths"):
+ paths = ",".join(settings["depot-paths"])
+ branchByDepotPath[paths] = "remotes/p4/" + branch
+
+ settings = None
+ parent = 0
+ while parent < 65535:
+ commit = head + "~%s" % parent
+ log = extractLogMessageFromGitCommit(commit)
+ settings = extractSettingsGitLog(log)
+ if settings.has_key("depot-paths"):
+ paths = ",".join(settings["depot-paths"])
+ if branchByDepotPath.has_key(paths):
+ return [branchByDepotPath[paths], settings]
+
+ parent = parent + 1
+
+ return ["", settings]
+
+def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
+ if not silent:
+ print ("Creating/updating branch(es) in %s based on origin branch(es)"
+ % localRefPrefix)
+
+ originPrefix = "origin/p4/"
+
+ for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
+ line = line.strip()
+ if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
+ continue
+
+ headName = line[len(originPrefix):]
+ remoteHead = localRefPrefix + headName
+ originHead = line
+
+ original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
+ if (not original.has_key('depot-paths')
+ or not original.has_key('change')):
+ continue
+
+ update = False
+ if not gitBranchExists(remoteHead):
+ if verbose:
+ print "creating %s" % remoteHead
+ update = True
+ else:
+ settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
+ if settings.has_key('change') > 0:
+ if settings['depot-paths'] == original['depot-paths']:
+ originP4Change = int(original['change'])
+ p4Change = int(settings['change'])
+ if originP4Change > p4Change:
+ print ("%s (%s) is newer than %s (%s). "
+ "Updating p4 branch from origin."
+ % (originHead, originP4Change,
+ remoteHead, p4Change))
+ update = True
+ else:
+ print ("Ignoring: %s was imported from %s while "
+ "%s was imported from %s"
+ % (originHead, ','.join(original['depot-paths']),
+ remoteHead, ','.join(settings['depot-paths'])))
+
+ if update:
+ system("git update-ref %s %s" % (remoteHead, originHead))
+
+def originP4BranchesExist():
+ return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
+
+def p4ChangesForPaths(depotPaths, changeRange):
+ assert depotPaths
+ cmd = ['changes']
+ for p in depotPaths:
+ cmd += ["%s...%s" % (p, changeRange)]
+ output = p4_read_pipe_lines(cmd)
+
+ changes = {}
+ for line in output:
+ changeNum = int(line.split(" ")[1])
+ changes[changeNum] = True
+
+ changelist = changes.keys()
+ changelist.sort()
+ return changelist
+
+def p4PathStartsWith(path, prefix):
+ # This method tries to remedy a potential mixed-case issue:
+ #
+ # If UserA adds //depot/DirA/file1
+ # and UserB adds //depot/dira/file2
+ #
+ # we may or may not have a problem. If you have core.ignorecase=true,
+ # we treat DirA and dira as the same directory
+ ignorecase = gitConfig("core.ignorecase", "--bool") == "true"
+ if ignorecase:
+ return path.lower().startswith(prefix.lower())
+ return path.startswith(prefix)
+
+def getClientSpec():
+ """Look at the p4 client spec, create a View() object that contains
+ all the mappings, and return it."""
+
+ specList = p4CmdList("client -o")
+ if len(specList) != 1:
+ die('Output from "client -o" is %d lines, expecting 1' %
+ len(specList))
+
+ # dictionary of all client parameters
+ entry = specList[0]
+
+ # just the keys that start with "View"
+ view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+
+ # hold this new View
+ view = View()
+
+ # append the lines, in order, to the view
+ for view_num in range(len(view_keys)):
+ k = "View%d" % view_num
+ if k not in view_keys:
+ die("Expected view key %s missing" % k)
+ view.append(entry[k])
+
+ return view
+
+def getClientRoot():
+ """Grab the client directory."""
+
+ output = p4CmdList("client -o")
+ if len(output) != 1:
+ die('Output from "client -o" is %d lines, expecting 1' % len(output))
+
+ entry = output[0]
+ if "Root" not in entry:
+ die('Client has no "Root"')
+
+ return entry["Root"]
+
+#
+# P4 wildcards are not allowed in filenames. P4 complains
+# if you simply add them, but you can force it with "-f", in
+# which case it translates them into %xx encoding internally.
+#
+def wildcard_decode(path):
+ # Search for and fix just these four characters. Do % last so
+ # that fixing it does not inadvertently create new %-escapes.
+ # Cannot have * in a filename in windows; untested as to
+ # what p4 would do in such a case.
+ if not platform.system() == "Windows":
+ path = path.replace("%2A", "*")
+ path = path.replace("%23", "#") \
+ .replace("%40", "@") \
+ .replace("%25", "%")
+ return path
+
+def wildcard_encode(path):
+ # do % first to avoid double-encoding the %s introduced here
+ path = path.replace("%", "%25") \
+ .replace("*", "%2A") \
+ .replace("#", "%23") \
+ .replace("@", "%40")
+ return path
+
+def wildcard_present(path):
+ return path.translate(None, "*#@%") != path
+
+class Command:
+ def __init__(self):
+ self.usage = "usage: %prog [options]"
+ self.needsGit = True
+ self.verbose = False
+
+class P4UserMap:
+ def __init__(self):
+ self.userMapFromPerforceServer = False
+ self.myP4UserId = None
+
+ def p4UserId(self):
+ if self.myP4UserId:
+ return self.myP4UserId
+
+ results = p4CmdList("user -o")
+ for r in results:
+ if r.has_key('User'):
+ self.myP4UserId = r['User']
+ return r['User']
+ die("Could not find your p4 user id")
+
+ def p4UserIsMe(self, p4User):
+ # return True if the given p4 user is actually me
+ me = self.p4UserId()
+ if not p4User or p4User != me:
+ return False
+ else:
+ return True
+
+ def getUserCacheFilename(self):
+ home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+ return home + "/.gitp4-usercache.txt"
+
+ def getUserMapFromPerforceServer(self):
+ if self.userMapFromPerforceServer:
+ return
+ self.users = {}
+ self.emails = {}
+
+ for output in p4CmdList("users"):
+ if not output.has_key("User"):
+ continue
+ self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+ self.emails[output["Email"]] = output["User"]
+
+
+ s = ''
+ for (key, val) in self.users.items():
+ s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+
+ open(self.getUserCacheFilename(), "wb").write(s)
+ self.userMapFromPerforceServer = True
+
+ def loadUserMapFromCache(self):
+ self.users = {}
+ self.userMapFromPerforceServer = False
+ try:
+ cache = open(self.getUserCacheFilename(), "rb")
+ lines = cache.readlines()
+ cache.close()
+ for line in lines:
+ entry = line.strip().split("\t")
+ self.users[entry[0]] = entry[1]
+ except IOError:
+ self.getUserMapFromPerforceServer()
+
+class P4Debug(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = []
+ self.description = "A tool to debug the output of p4 -G."
+ self.needsGit = False
+
+ def run(self, args):
+ j = 0
+ for output in p4CmdList(args):
+ print 'Element: %d' % j
+ j += 1
+ print output
+ return True
+
+class P4RollBack(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
+ ]
+ self.description = "A tool to debug the multi-branch import. Don't use :)"
+ self.rollbackLocalBranches = False
+
+ def run(self, args):
+ if len(args) != 1:
+ return False
+ maxChange = int(args[0])
+
+ if "p4ExitCode" in p4Cmd("changes -m 1"):
+ die("Problems executing p4");
+
+ if self.rollbackLocalBranches:
+ refPrefix = "refs/heads/"
+ lines = read_pipe_lines("git rev-parse --symbolic --branches")
+ else:
+ refPrefix = "refs/remotes/"
+ lines = read_pipe_lines("git rev-parse --symbolic --remotes")
+
+ for line in lines:
+ if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
+ line = line.strip()
+ ref = refPrefix + line
+ log = extractLogMessageFromGitCommit(ref)
+ settings = extractSettingsGitLog(log)
+
+ depotPaths = settings['depot-paths']
+ change = settings['change']
+
+ changed = False
+
+ if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
+ for p in depotPaths]))) == 0:
+ print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
+ system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
+ continue
+
+ while change and int(change) > maxChange:
+ changed = True
+ if self.verbose:
+ print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
+ system("git update-ref %s \"%s^\"" % (ref, ref))
+ log = extractLogMessageFromGitCommit(ref)
+ settings = extractSettingsGitLog(log)
+
+
+ depotPaths = settings['depot-paths']
+ change = settings['change']
+
+ if changed:
+ print "%s rewound to %s" % (ref, change)
+
+ return True
+
+class P4Submit(Command, P4UserMap):
+ def __init__(self):
+ Command.__init__(self)
+ P4UserMap.__init__(self)
+ self.options = [
+ optparse.make_option("--origin", dest="origin"),
+ optparse.make_option("-M", dest="detectRenames", action="store_true"),
+ # preserve the user, requires relevant p4 permissions
+ optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
+ optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
+ ]
+ self.description = "Submit changes from git to the perforce depot."
+ self.usage += " [name of git branch to submit into perforce depot]"
+ self.interactive = True
+ self.origin = ""
+ self.detectRenames = False
+ self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
+ self.isWindows = (platform.system() == "Windows")
+ self.exportLabels = False
+
+ def check(self):
+ if len(p4CmdList("opened ...")) > 0:
+ die("You have files opened with perforce! Close them before starting the sync.")
+
+ # replaces everything between 'Description:' and the next P4 submit template field with the
+ # commit message
+ def prepareLogMessage(self, template, message):
+ result = ""
+
+ inDescriptionSection = False
+
+ for line in template.split("\n"):
+ if line.startswith("#"):
+ result += line + "\n"
+ continue
+
+ if inDescriptionSection:
+ if line.startswith("Files:") or line.startswith("Jobs:"):
+ inDescriptionSection = False
+ else:
+ continue
+ else:
+ if line.startswith("Description:"):
+ inDescriptionSection = True
+ line += "\n"
+ for messageLine in message.split("\n"):
+ line += "\t" + messageLine + "\n"
+
+ result += line + "\n"
+
+ return result
+
+ def patchRCSKeywords(self, file, pattern):
+ # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
+ (handle, outFileName) = tempfile.mkstemp(dir='.')
+ try:
+ outFile = os.fdopen(handle, "w+")
+ inFile = open(file, "r")
+ regexp = re.compile(pattern, re.VERBOSE)
+ for line in inFile.readlines():
+ line = regexp.sub(r'$\1$', line)
+ outFile.write(line)
+ inFile.close()
+ outFile.close()
+ # Forcibly overwrite the original file
+ os.unlink(file)
+ shutil.move(outFileName, file)
+ except:
+ # cleanup our temporary file
+ os.unlink(outFileName)
+ print "Failed to strip RCS keywords in %s" % file
+ raise
+
+ print "Patched up RCS keywords in %s" % file
+
+ def p4UserForCommit(self,id):
+ # Return the tuple (perforce user,git email) for a given git commit id
+ self.getUserMapFromPerforceServer()
+ gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
+ gitEmail = gitEmail.strip()
+ if not self.emails.has_key(gitEmail):
+ return (None,gitEmail)
+ else:
+ return (self.emails[gitEmail],gitEmail)
+
+ def checkValidP4Users(self,commits):
+ # check if any git authors cannot be mapped to p4 users
+ for id in commits:
+ (user,email) = self.p4UserForCommit(id)
+ if not user:
+ msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
+ if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
+ print "%s" % msg
+ else:
+ die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
+
+ def lastP4Changelist(self):
+ # Get back the last changelist number submitted in this client spec. This
+ # then gets used to patch up the username in the change. If the same
+ # client spec is being used by multiple processes then this might go
+ # wrong.
+ results = p4CmdList("client -o") # find the current client
+ client = None
+ for r in results:
+ if r.has_key('Client'):
+ client = r['Client']
+ break
+ if not client:
+ die("could not get client spec")
+ results = p4CmdList(["changes", "-c", client, "-m", "1"])
+ for r in results:
+ if r.has_key('change'):
+ return r['change']
+ die("Could not get changelist number for last submit - cannot patch up user details")
+
+ def modifyChangelistUser(self, changelist, newUser):
+ # fixup the user field of a changelist after it has been submitted.
+ changes = p4CmdList("change -o %s" % changelist)
+ if len(changes) != 1:
+ die("Bad output from p4 change modifying %s to user %s" %
+ (changelist, newUser))
+
+ c = changes[0]
+ if c['User'] == newUser: return # nothing to do
+ c['User'] = newUser
+ input = marshal.dumps(c)
+
+ result = p4CmdList("change -f -i", stdin=input)
+ for r in result:
+ if r.has_key('code'):
+ if r['code'] == 'error':
+ die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
+ if r.has_key('data'):
+ print("Updated user field for changelist %s to %s" % (changelist, newUser))
+ return
+ die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
+
+ def canChangeChangelists(self):
+ # check to see if we have p4 admin or super-user permissions, either of
+ # which are required to modify changelists.
+ results = p4CmdList(["protects", self.depotPath])
+ for r in results:
+ if r.has_key('perm'):
+ if r['perm'] == 'admin':
+ return 1
+ if r['perm'] == 'super':
+ return 1
+ return 0
+
+ def prepareSubmitTemplate(self):
+ # remove lines in the Files section that show changes to files outside the depot path we're committing into
+ template = ""
+ inFilesSection = False
+ for line in p4_read_pipe_lines(['change', '-o']):
+ if line.endswith("\r\n"):
+ line = line[:-2] + "\n"
+ if inFilesSection:
+ if line.startswith("\t"):
+ # path starts and ends with a tab
+ path = line[1:]
+ lastTab = path.rfind("\t")
+ if lastTab != -1:
+ path = path[:lastTab]
+ if not p4PathStartsWith(path, self.depotPath):
+ continue
+ else:
+ inFilesSection = False
+ else:
+ if line.startswith("Files:"):
+ inFilesSection = True
+
+ template += line
+
+ return template
+
+ def edit_template(self, template_file):
+ """Invoke the editor to let the user change the submission
+ message. Return true if okay to continue with the submit."""
+
+ # if configured to skip the editing part, just submit
+ if gitConfig("git-p4.skipSubmitEdit") == "true":
+ return True
+
+ # look at the modification time, to check later if the user saved
+ # the file
+ mtime = os.stat(template_file).st_mtime
+
+ # invoke the editor
+ if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""):
+ editor = os.environ.get("P4EDITOR")
+ else:
+ editor = read_pipe("git var GIT_EDITOR").strip()
+ system(editor + " " + template_file)
+
+ # If the file was not saved, prompt to see if this patch should
+ # be skipped. But skip this verification step if configured so.
+ if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+ return True
+
+ # modification time updated means user saved the file
+ if os.stat(template_file).st_mtime > mtime:
+ return True
+
+ while True:
+ response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+ if response == 'y':
+ return True
+ if response == 'n':
+ return False
+
+ def applyCommit(self, id):
+ print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+
+ (p4User, gitEmail) = self.p4UserForCommit(id)
+
+ if not self.detectRenames:
+ # If not explicitly set check the config variable
+ self.detectRenames = gitConfig("git-p4.detectRenames")
+
+ if self.detectRenames.lower() == "false" or self.detectRenames == "":
+ diffOpts = ""
+ elif self.detectRenames.lower() == "true":
+ diffOpts = "-M"
+ else:
+ diffOpts = "-M%s" % self.detectRenames
+
+ detectCopies = gitConfig("git-p4.detectCopies")
+ if detectCopies.lower() == "true":
+ diffOpts += " -C"
+ elif detectCopies != "" and detectCopies.lower() != "false":
+ diffOpts += " -C%s" % detectCopies
+
+ if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
+ diffOpts += " --find-copies-harder"
+
+ diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+ filesToAdd = set()
+ filesToDelete = set()
+ editedFiles = set()
+ pureRenameCopy = set()
+ filesToChangeExecBit = {}
+
+ for line in diff:
+ diff = parseDiffTreeEntry(line)
+ modifier = diff['status']
+ path = diff['src']
+ if modifier == "M":
+ p4_edit(path)
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ filesToChangeExecBit[path] = diff['dst_mode']
+ editedFiles.add(path)
+ elif modifier == "A":
+ filesToAdd.add(path)
+ filesToChangeExecBit[path] = diff['dst_mode']
+ if path in filesToDelete:
+ filesToDelete.remove(path)
+ elif modifier == "D":
+ filesToDelete.add(path)
+ if path in filesToAdd:
+ filesToAdd.remove(path)
+ elif modifier == "C":
+ src, dest = diff['src'], diff['dst']
+ p4_integrate(src, dest)
+ pureRenameCopy.add(dest)
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_edit(dest)
+ pureRenameCopy.discard(dest)
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_edit(dest)
+ pureRenameCopy.discard(dest)
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
+ elif modifier == "R":
+ src, dest = diff['src'], diff['dst']
+ p4_integrate(src, dest)
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_edit(dest)
+ else:
+ pureRenameCopy.add(dest)
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_edit(dest)
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
+ filesToDelete.add(src)
+ else:
+ die("unknown modifier %s for %s" % (modifier, path))
+
+ diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+ patchcmd = diffcmd + " | git apply "
+ tryPatchCmd = patchcmd + "--check -"
+ applyPatchCmd = patchcmd + "--check --apply -"
+ patch_succeeded = True
+
+ if os.system(tryPatchCmd) != 0:
+ fixed_rcs_keywords = False
+ patch_succeeded = False
+ print "Unfortunately applying the change failed!"
+
+ # Patch failed, maybe it's just RCS keyword woes. Look through
+ # the patch to see if that's possible.
+ if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
+ file = None
+ pattern = None
+ kwfiles = {}
+ for file in editedFiles | filesToDelete:
+ # did this file's delta contain RCS keywords?
+ pattern = p4_keywords_regexp_for_file(file)
+
+ if pattern:
+ # this file is a possibility...look for RCS keywords.
+ regexp = re.compile(pattern, re.VERBOSE)
+ for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
+ if regexp.search(line):
+ if verbose:
+ print "got keyword match on %s in %s in %s" % (pattern, line, file)
+ kwfiles[file] = pattern
+ break
+
+ for file in kwfiles:
+ if verbose:
+ print "zapping %s with %s" % (line,pattern)
+ self.patchRCSKeywords(file, kwfiles[file])
+ fixed_rcs_keywords = True
+
+ if fixed_rcs_keywords:
+ print "Retrying the patch with RCS keywords cleaned up"
+ if os.system(tryPatchCmd) == 0:
+ patch_succeeded = True
+
+ if not patch_succeeded:
+ print "What do you want to do?"
+ response = "x"
+ while response != "s" and response != "a" and response != "w":
+ response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
+ "and with .rej files / [w]rite the patch to a file (patch.txt) ")
+ if response == "s":
+ print "Skipping! Good luck with the next patches..."
+ for f in editedFiles:
+ p4_revert(f)
+ for f in filesToAdd:
+ os.remove(f)
+ return
+ elif response == "a":
+ os.system(applyPatchCmd)
+ if len(filesToAdd) > 0:
+ print "You may also want to call p4 add on the following files:"
+ print " ".join(filesToAdd)
+ if len(filesToDelete):
+ print "The following files should be scheduled for deletion with p4 delete:"
+ print " ".join(filesToDelete)
+ die("Please resolve and submit the conflict manually and "
+ + "continue afterwards with git p4 submit --continue")
+ elif response == "w":
+ system(diffcmd + " > patch.txt")
+ print "Patch saved to patch.txt in %s !" % self.clientPath
+ die("Please resolve and submit the conflict manually and "
+ "continue afterwards with git p4 submit --continue")
+
+ system(applyPatchCmd)
+
+ for f in filesToAdd:
+ p4_add(f)
+ for f in filesToDelete:
+ p4_revert(f)
+ p4_delete(f)
+
+ # Set/clear executable bits
+ for f in filesToChangeExecBit.keys():
+ mode = filesToChangeExecBit[f]
+ setP4ExecBit(f, mode)
+
+ logMessage = extractLogMessageFromGitCommit(id)
+ logMessage = logMessage.strip()
+
+ template = self.prepareSubmitTemplate()
+
+ if self.interactive:
+ submitTemplate = self.prepareLogMessage(template, logMessage)
+
+ if self.preserveUser:
+ submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+
+ if os.environ.has_key("P4DIFF"):
+ del(os.environ["P4DIFF"])
+ diff = ""
+ for editedFile in editedFiles:
+ diff += p4_read_pipe(['diff', '-du',
+ wildcard_encode(editedFile)])
+
+ newdiff = ""
+ for newFile in filesToAdd:
+ newdiff += "==== new file ====\n"
+ newdiff += "--- /dev/null\n"
+ newdiff += "+++ %s\n" % newFile
+ f = open(newFile, "r")
+ for line in f.readlines():
+ newdiff += "+" + line
+ f.close()
+
+ if self.checkAuthorship and not self.p4UserIsMe(p4User):
+ submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+ submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
+ submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
+
+ separatorLine = "######## everything below this line is just the diff #######\n"
+
+ (handle, fileName) = tempfile.mkstemp()
+ tmpFile = os.fdopen(handle, "w+")
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\n", "\r\n")
+ separatorLine = separatorLine.replace("\n", "\r\n")
+ newdiff = newdiff.replace("\n", "\r\n")
+ tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
+ tmpFile.close()
+
+ if self.edit_template(fileName):
+ # read the edited message and submit
+ tmpFile = open(fileName, "rb")
+ message = tmpFile.read()
+ tmpFile.close()
+ submitTemplate = message[:message.index(separatorLine)]
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\r\n", "\n")
+ p4_write_pipe(['submit', '-i'], submitTemplate)
+
+ if self.preserveUser:
+ if p4User:
+ # Get last changelist number. Cannot easily get it from
+ # the submit command output as the output is
+ # unmarshalled.
+ changelist = self.lastP4Changelist()
+ self.modifyChangelistUser(changelist, p4User)
+
+ # The rename/copy happened by applying a patch that created a
+ # new file. This leaves it writable, which confuses p4.
+ for f in pureRenameCopy:
+ p4_sync(f, "-f")
+
+ else:
+ # skip this patch
+ print "Submission cancelled, undoing p4 changes."
+ for f in editedFiles:
+ p4_revert(f)
+ for f in filesToAdd:
+ p4_revert(f)
+ os.remove(f)
+
+ os.remove(fileName)
+ else:
+ fileName = "submit.txt"
+ file = open(fileName, "w+")
+ file.write(self.prepareLogMessage(template, logMessage))
+ file.close()
+ print ("Perforce submit template written as %s. "
+ + "Please review/edit and then use p4 submit -i < %s to submit directly!"
+ % (fileName, fileName))
+
+ # Export git tags as p4 labels. Create a p4 label and then tag
+ # with that.
+ def exportGitTags(self, gitTags):
+ validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
+ if len(validLabelRegexp) == 0:
+ validLabelRegexp = defaultLabelRegexp
+ m = re.compile(validLabelRegexp)
+
+ for name in gitTags:
+
+ if not m.match(name):
+ if verbose:
+ print "tag %s does not match regexp %s" % (name, validLabelRegexp)
+ continue
+
+ # Get the p4 commit this corresponds to
+ logMessage = extractLogMessageFromGitCommit(name)
+ values = extractSettingsGitLog(logMessage)
+
+ if not values.has_key('change'):
+ # a tag pointing to something not sent to p4; ignore
+ if verbose:
+ print "git tag %s does not give a p4 commit" % name
+ continue
+ else:
+ changelist = values['change']
+
+ # Get the tag details.
+ inHeader = True
+ isAnnotated = False
+ body = []
+ for l in read_pipe_lines(["git", "cat-file", "-p", name]):
+ l = l.strip()
+ if inHeader:
+ if re.match(r'tag\s+', l):
+ isAnnotated = True
+ elif re.match(r'\s*$', l):
+ inHeader = False
+ continue
+ else:
+ body.append(l)
+
+ if not isAnnotated:
+ body = ["lightweight tag imported by git p4\n"]
+
+ # Create the label - use the same view as the client spec we are using
+ clientSpec = getClientSpec()
+
+ labelTemplate = "Label: %s\n" % name
+ labelTemplate += "Description:\n"
+ for b in body:
+ labelTemplate += "\t" + b + "\n"
+ labelTemplate += "View:\n"
+ for mapping in clientSpec.mappings:
+ labelTemplate += "\t%s\n" % mapping.depot_side.path
+
+ p4_write_pipe(["label", "-i"], labelTemplate)
+
+ # Use the label
+ p4_system(["tag", "-l", name] +
+ ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
+
+ if verbose:
+ print "created p4 label for tag %s" % name
+
+ def run(self, args):
+ if len(args) == 0:
+ self.master = currentGitBranch()
+ if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
+ die("Detecting current git branch failed!")
+ elif len(args) == 1:
+ self.master = args[0]
+ if not branchExists(self.master):
+ die("Branch %s does not exist" % self.master)
+ else:
+ return False
+
+ allowSubmit = gitConfig("git-p4.allowSubmit")
+ if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
+ die("%s is not in git-p4.allowSubmit" % self.master)
+
+ [upstream, settings] = findUpstreamBranchPoint()
+ self.depotPath = settings['depot-paths'][0]
+ if len(self.origin) == 0:
+ self.origin = upstream
+
+ if self.preserveUser:
+ if not self.canChangeChangelists():
+ die("Cannot preserve user names without p4 super-user or admin permissions")
+
+ if self.verbose:
+ print "Origin branch is " + self.origin
+
+ if len(self.depotPath) == 0:
+ print "Internal error: cannot locate perforce depot path from existing branches"
+ sys.exit(128)
+
+ self.useClientSpec = False
+ if gitConfig("git-p4.useclientspec", "--bool") == "true":
+ self.useClientSpec = True
+ if self.useClientSpec:
+ self.clientSpecDirs = getClientSpec()
+
+ if self.useClientSpec:
+ # all files are relative to the client spec
+ self.clientPath = getClientRoot()
+ else:
+ self.clientPath = p4Where(self.depotPath)
+
+ if self.clientPath == "":
+ die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
+
+ print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
+ self.oldWorkingDirectory = os.getcwd()
+
+ # ensure the clientPath exists
+ new_client_dir = False
+ if not os.path.exists(self.clientPath):
+ new_client_dir = True
+ os.makedirs(self.clientPath)
+
+ chdir(self.clientPath)
+ print "Synchronizing p4 checkout..."
+ if new_client_dir:
+ # old one was destroyed, and maybe nobody told p4
+ p4_sync("...", "-f")
+ else:
+ p4_sync("...")
+ self.check()
+
+ commits = []
+ for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+ commits.append(line.strip())
+ commits.reverse()
+
+ if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
+ self.checkAuthorship = False
+ else:
+ self.checkAuthorship = True
+
+ if self.preserveUser:
+ self.checkValidP4Users(commits)
+
+ while len(commits) > 0:
+ commit = commits[0]
+ commits = commits[1:]
+ self.applyCommit(commit)
+ if not self.interactive:
+ break
+
+ if len(commits) == 0:
+ print "All changes applied!"
+ chdir(self.oldWorkingDirectory)
+
+ sync = P4Sync()
+ sync.run([])
+
+ rebase = P4Rebase()
+ rebase.rebase()
+
+ if gitConfig("git-p4.exportLabels", "--bool") == "true":
+ self.exportLabels = True
+
+ if self.exportLabels:
+ p4Labels = getP4Labels(self.depotPath)
+ gitTags = getGitTags()
+
+ missingGitTags = gitTags - p4Labels
+ self.exportGitTags(missingGitTags)
+
+ return True
+
+class View(object):
+ """Represent a p4 view ("p4 help views"), and map files in a
+ repo according to the view."""
+
+ class Path(object):
+ """A depot or client path, possibly containing wildcards.
+ The only one supported is ... at the end, currently.
+ Initialize with the full path, with //depot or //client."""
+
+ def __init__(self, path, is_depot):
+ self.path = path
+ self.is_depot = is_depot
+ self.find_wildcards()
+ # remember the prefix bit, useful for relative mappings
+ m = re.match("(//[^/]+/)", self.path)
+ if not m:
+ die("Path %s does not start with //prefix/" % self.path)
+ prefix = m.group(1)
+ if not self.is_depot:
+ # strip //client/ on client paths
+ self.path = self.path[len(prefix):]
+
+ def find_wildcards(self):
+ """Make sure wildcards are valid, and set up internal
+ variables."""
+
+ self.ends_triple_dot = False
+ # There are three wildcards allowed in p4 views
+ # (see "p4 help views"). This code knows how to
+ # handle "..." (only at the end), but cannot deal with
+ # "%%n" or "*". Only check the depot_side, as p4 should
+ # validate that the client_side matches too.
+ if re.search(r'%%[1-9]', self.path):
+ die("Can't handle %%n wildcards in view: %s" % self.path)
+ if self.path.find("*") >= 0:
+ die("Can't handle * wildcards in view: %s" % self.path)
+ triple_dot_index = self.path.find("...")
+ if triple_dot_index >= 0:
+ if triple_dot_index != len(self.path) - 3:
+ die("Can handle only single ... wildcard, at end: %s" %
+ self.path)
+ self.ends_triple_dot = True
+
+ def ensure_compatible(self, other_path):
+ """Make sure the wildcards agree."""
+ if self.ends_triple_dot != other_path.ends_triple_dot:
+ die("Both paths must end with ... if either does;\n" +
+ "paths: %s %s" % (self.path, other_path.path))
+
+ def match_wildcards(self, test_path):
+ """See if this test_path matches us, and fill in the value
+ of the wildcards if so. Returns a tuple of
+ (True|False, wildcards[]). For now, only the ... at end
+ is supported, so at most one wildcard."""
+ if self.ends_triple_dot:
+ dotless = self.path[:-3]
+ if test_path.startswith(dotless):
+ wildcard = test_path[len(dotless):]
+ return (True, [ wildcard ])
+ else:
+ if test_path == self.path:
+ return (True, [])
+ return (False, [])
+
+ def match(self, test_path):
+ """Just return if it matches; don't bother with the wildcards."""
+ b, _ = self.match_wildcards(test_path)
+ return b
+
+ def fill_in_wildcards(self, wildcards):
+ """Return the relative path, with the wildcards filled in
+ if there are any."""
+ if self.ends_triple_dot:
+ return self.path[:-3] + wildcards[0]
+ else:
+ return self.path
+
+ class Mapping(object):
+ def __init__(self, depot_side, client_side, overlay, exclude):
+ # depot_side is without the trailing /... if it had one
+ self.depot_side = View.Path(depot_side, is_depot=True)
+ self.client_side = View.Path(client_side, is_depot=False)
+ self.overlay = overlay # started with "+"
+ self.exclude = exclude # started with "-"
+ assert not (self.overlay and self.exclude)
+ self.depot_side.ensure_compatible(self.client_side)
+
+ def __str__(self):
+ c = " "
+ if self.overlay:
+ c = "+"
+ if self.exclude:
+ c = "-"
+ return "View.Mapping: %s%s -> %s" % \
+ (c, self.depot_side.path, self.client_side.path)
+
+ def map_depot_to_client(self, depot_path):
+ """Calculate the client path if using this mapping on the
+ given depot path; does not consider the effect of other
+ mappings in a view. Even excluded mappings are returned."""
+ matches, wildcards = self.depot_side.match_wildcards(depot_path)
+ if not matches:
+ return ""
+ client_path = self.client_side.fill_in_wildcards(wildcards)
+ return client_path
+
+ #
+ # View methods
+ #
+ def __init__(self):
+ self.mappings = []
+
+ def append(self, view_line):
+ """Parse a view line, splitting it into depot and client
+ sides. Append to self.mappings, preserving order."""
+
+ # Split the view line into exactly two words. P4 enforces
+ # structure on these lines that simplifies this quite a bit.
+ #
+ # Either or both words may be double-quoted.
+ # Single quotes do not matter.
+ # Double-quote marks cannot occur inside the words.
+ # A + or - prefix is also inside the quotes.
+ # There are no quotes unless they contain a space.
+ # The line is already white-space stripped.
+ # The two words are separated by a single space.
+ #
+ if view_line[0] == '"':
+ # First word is double quoted. Find its end.
+ close_quote_index = view_line.find('"', 1)
+ if close_quote_index <= 0:
+ die("No first-word closing quote found: %s" % view_line)
+ depot_side = view_line[1:close_quote_index]
+ # skip closing quote and space
+ rhs_index = close_quote_index + 1 + 1
+ else:
+ space_index = view_line.find(" ")
+ if space_index <= 0:
+ die("No word-splitting space found: %s" % view_line)
+ depot_side = view_line[0:space_index]
+ rhs_index = space_index + 1
+
+ if view_line[rhs_index] == '"':
+ # Second word is double quoted. Make sure there is a
+ # double quote at the end too.
+ if not view_line.endswith('"'):
+ die("View line with rhs quote should end with one: %s" %
+ view_line)
+ # skip the quotes
+ client_side = view_line[rhs_index+1:-1]
+ else:
+ client_side = view_line[rhs_index:]
+
+ # prefix + means overlay on previous mapping
+ overlay = False
+ if depot_side.startswith("+"):
+ overlay = True
+ depot_side = depot_side[1:]
+
+ # prefix - means exclude this path
+ exclude = False
+ if depot_side.startswith("-"):
+ exclude = True
+ depot_side = depot_side[1:]
+
+ m = View.Mapping(depot_side, client_side, overlay, exclude)
+ self.mappings.append(m)
+
+ def map_in_client(self, depot_path):
+ """Return the relative location in the client where this
+ depot file should live. Returns "" if the file should
+ not be mapped in the client."""
+
+ paths_filled = []
+ client_path = ""
+
+ # look at later entries first
+ for m in self.mappings[::-1]:
+
+ # see where will this path end up in the client
+ p = m.map_depot_to_client(depot_path)
+
+ if p == "":
+ # Depot path does not belong in client. Must remember
+ # this, as previous items should not cause files to
+ # exist in this path either. Remember that the list is
+ # being walked from the end, which has higher precedence.
+ # Overlap mappings do not exclude previous mappings.
+ if not m.overlay:
+ paths_filled.append(m.client_side)
+
+ else:
+ # This mapping matched; no need to search any further.
+ # But, the mapping could be rejected if the client path
+ # has already been claimed by an earlier mapping (i.e.
+ # one later in the list, which we are walking backwards).
+ already_mapped_in_client = False
+ for f in paths_filled:
+ # this is View.Path.match
+ if f.match(p):
+ already_mapped_in_client = True
+ break
+ if not already_mapped_in_client:
+ # Include this file, unless it is from a line that
+ # explicitly said to exclude it.
+ if not m.exclude:
+ client_path = p
+
+ # a match, even if rejected, always stops the search
+ break
+
+ return client_path
+
+class P4Sync(Command, P4UserMap):
+ delete_actions = ( "delete", "move/delete", "purge" )
+
+ def __init__(self):
+ Command.__init__(self)
+ P4UserMap.__init__(self)
+ self.options = [
+ optparse.make_option("--branch", dest="branch"),
+ optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
+ optparse.make_option("--changesfile", dest="changesFile"),
+ optparse.make_option("--silent", dest="silent", action="store_true"),
+ optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
+ optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
+ help="Import into refs/heads/ , not refs/remotes"),
+ optparse.make_option("--max-changes", dest="maxChanges"),
+ optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
+ help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
+ optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
+ help="Only sync files that are included in the Perforce Client Spec")
+ ]
+ self.description = """Imports from Perforce into a git repository.\n
+ example:
+ //depot/my/project/ -- to import the current head
+ //depot/my/project/@all -- to import everything
+ //depot/my/project/@1,6 -- to import only from revision 1 to 6
+
+ (a ... is not needed in the path p4 specification, it's added implicitly)"""
+
+ self.usage += " //depot/path[@revRange]"
+ self.silent = False
+ self.createdBranches = set()
+ self.committedChanges = set()
+ self.branch = ""
+ self.detectBranches = False
+ self.detectLabels = False
+ self.importLabels = False
+ self.changesFile = ""
+ self.syncWithOrigin = True
+ self.importIntoRemotes = True
+ self.maxChanges = ""
+ self.isWindows = (platform.system() == "Windows")
+ self.keepRepoPath = False
+ self.depotPaths = None
+ self.p4BranchesInGit = []
+ self.cloneExclude = []
+ self.useClientSpec = False
+ self.useClientSpec_from_options = False
+ self.clientSpecDirs = None
+ self.tempBranches = []
+ self.tempBranchLocation = "git-p4-tmp"
+
+ if gitConfig("git-p4.syncFromOrigin") == "false":
+ self.syncWithOrigin = False
+
+ # Force a checkpoint in fast-import and wait for it to finish
+ def checkpoint(self):
+ self.gitStream.write("checkpoint\n\n")
+ self.gitStream.write("progress checkpoint\n\n")
+ out = self.gitOutput.readline()
+ if self.verbose:
+ print "checkpoint finished: " + out
+
+ def extractFilesFromCommit(self, commit):
+ self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
+ for path in self.cloneExclude]
+ files = []
+ fnum = 0
+ while commit.has_key("depotFile%s" % fnum):
+ path = commit["depotFile%s" % fnum]
+
+ if [p for p in self.cloneExclude
+ if p4PathStartsWith(path, p)]:
+ found = False
+ else:
+ found = [p for p in self.depotPaths
+ if p4PathStartsWith(path, p)]
+ if not found:
+ fnum = fnum + 1
+ continue
+
+ file = {}
+ file["path"] = path
+ file["rev"] = commit["rev%s" % fnum]
+ file["action"] = commit["action%s" % fnum]
+ file["type"] = commit["type%s" % fnum]
+ files.append(file)
+ fnum = fnum + 1
+ return files
+
+ def stripRepoPath(self, path, prefixes):
+ if self.useClientSpec:
+ return self.clientSpecDirs.map_in_client(path)
+
+ if self.keepRepoPath:
+ prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
+
+ for p in prefixes:
+ if p4PathStartsWith(path, p):
+ path = path[len(p):]
+
+ return path
+
+ def splitFilesIntoBranches(self, commit):
+ branches = {}
+ fnum = 0
+ while commit.has_key("depotFile%s" % fnum):
+ path = commit["depotFile%s" % fnum]
+ found = [p for p in self.depotPaths
+ if p4PathStartsWith(path, p)]
+ if not found:
+ fnum = fnum + 1
+ continue
+
+ file = {}
+ file["path"] = path
+ file["rev"] = commit["rev%s" % fnum]
+ file["action"] = commit["action%s" % fnum]
+ file["type"] = commit["type%s" % fnum]
+ fnum = fnum + 1
+
+ relPath = self.stripRepoPath(path, self.depotPaths)
+ relPath = wildcard_decode(relPath)
+
+ for branch in self.knownBranches.keys():
+
+ # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
+ if relPath.startswith(branch + "/"):
+ if branch not in branches:
+ branches[branch] = []
+ branches[branch].append(file)
+ break
+
+ return branches
+
+ # output one file from the P4 stream
+ # - helper for streamP4Files
+
+ def streamOneP4File(self, file, contents):
+ relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+ relPath = wildcard_decode(relPath)
+ if verbose:
+ sys.stderr.write("%s\n" % relPath)
+
+ (type_base, type_mods) = split_p4_type(file["type"])
+
+ git_mode = "100644"
+ if "x" in type_mods:
+ git_mode = "100755"
+ if type_base == "symlink":
+ git_mode = "120000"
+ # p4 print on a symlink contains "target\n"; remove the newline
+ data = ''.join(contents)
+ contents = [data[:-1]]
+
+ if type_base == "utf16":
+ # p4 delivers different text in the python output to -G
+ # than it does when using "print -o", or normal p4 client
+ # operations. utf16 is converted to ascii or utf8, perhaps.
+ # But ascii text saved as -t utf16 is completely mangled.
+ # Invoke print -o to get the real contents.
+ text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+ contents = [ text ]
+
+ if type_base == "apple":
+ # Apple filetype files will be streamed as a concatenation of
+ # its appledouble header and the contents. This is useless
+ # on both macs and non-macs. If using "print -q -o xx", it
+ # will create "xx" with the data, and "%xx" with the header.
+ # This is also not very useful.
+ #
+ # Ideally, someday, this script can learn how to generate
+ # appledouble files directly and import those to git, but
+ # non-mac machines can never find a use for apple filetype.
+ print "\nIgnoring apple filetype file %s" % file['depotFile']
+ return
+
+ # Perhaps windows wants unicode, utf16 newlines translated too;
+ # but this is not doing it.
+ if self.isWindows and type_base == "text":
+ mangled = []
+ for data in contents:
+ data = data.replace("\r\n", "\n")
+ mangled.append(data)
+ contents = mangled
+
+ # Note that we do not try to de-mangle keywords on utf16 files,
+ # even though in theory somebody may want that.
+ pattern = p4_keywords_regexp_for_type(type_base, type_mods)
+ if pattern:
+ regexp = re.compile(pattern, re.VERBOSE)
+ text = ''.join(contents)
+ text = regexp.sub(r'$\1$', text)
+ contents = [ text ]
+
+ self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
+
+ # total length...
+ length = 0
+ for d in contents:
+ length = length + len(d)
+
+ self.gitStream.write("data %d\n" % length)
+ for d in contents:
+ self.gitStream.write(d)
+ self.gitStream.write("\n")
+
+ def streamOneP4Deletion(self, file):
+ relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
+ relPath = wildcard_decode(relPath)
+ if verbose:
+ sys.stderr.write("delete %s\n" % relPath)
+ self.gitStream.write("D %s\n" % relPath)
+
+ # handle another chunk of streaming data
+ def streamP4FilesCb(self, marshalled):
+
+ if marshalled.has_key('depotFile') and self.stream_have_file_info:
+ # start of a new file - output the old one first
+ self.streamOneP4File(self.stream_file, self.stream_contents)
+ self.stream_file = {}
+ self.stream_contents = []
+ self.stream_have_file_info = False
+
+ # pick up the new file information... for the
+ # 'data' field we need to append to our array
+ for k in marshalled.keys():
+ if k == 'data':
+ self.stream_contents.append(marshalled['data'])
+ else:
+ self.stream_file[k] = marshalled[k]
+
+ self.stream_have_file_info = True
+
+ # Stream directly from "p4 files" into "git fast-import"
+ def streamP4Files(self, files):
+ filesForCommit = []
+ filesToRead = []
+ filesToDelete = []
+
+ for f in files:
+ # if using a client spec, only add the files that have
+ # a path in the client
+ if self.clientSpecDirs:
+ if self.clientSpecDirs.map_in_client(f['path']) == "":
+ continue
+
+ filesForCommit.append(f)
+ if f['action'] in self.delete_actions:
+ filesToDelete.append(f)
+ else:
+ filesToRead.append(f)
+
+ # deleted files...
+ for f in filesToDelete:
+ self.streamOneP4Deletion(f)
+
+ if len(filesToRead) > 0:
+ self.stream_file = {}
+ self.stream_contents = []
+ self.stream_have_file_info = False
+
+ # curry self argument
+ def streamP4FilesCbSelf(entry):
+ self.streamP4FilesCb(entry)
+
+ fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+
+ p4CmdList(["-x", "-", "print"],
+ stdin=fileArgs,
+ cb=streamP4FilesCbSelf)
+
+ # do the last chunk
+ if self.stream_file.has_key('depotFile'):
+ self.streamOneP4File(self.stream_file, self.stream_contents)
+
+ def make_email(self, userid):
+ if userid in self.users:
+ return self.users[userid]
+ else:
+ return "%s <a@b>" % userid
+
+ # Stream a p4 tag
+ def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
+ if verbose:
+ print "writing tag %s for commit %s" % (labelName, commit)
+ gitStream.write("tag %s\n" % labelName)
+ gitStream.write("from %s\n" % commit)
+
+ if labelDetails.has_key('Owner'):
+ owner = labelDetails["Owner"]
+ else:
+ owner = None
+
+ # Try to use the owner of the p4 label, or failing that,
+ # the current p4 user id.
+ if owner:
+ email = self.make_email(owner)
+ else:
+ email = self.make_email(self.p4UserId())
+ tagger = "%s %s %s" % (email, epoch, self.tz)
+
+ gitStream.write("tagger %s\n" % tagger)
+
+ print "labelDetails=",labelDetails
+ if labelDetails.has_key('Description'):
+ description = labelDetails['Description']
+ else:
+ description = 'Label from git p4'
+
+ gitStream.write("data %d\n" % len(description))
+ gitStream.write(description)
+ gitStream.write("\n")
+
+ def commit(self, details, files, branch, branchPrefixes, parent = ""):
+ epoch = details["time"]
+ author = details["user"]
+ self.branchPrefixes = branchPrefixes
+
+ if self.verbose:
+ print "commit into %s" % branch
+
+ # start with reading files; if that fails, we should not
+ # create a commit.
+ new_files = []
+ for f in files:
+ if [p for p in branchPrefixes if p4PathStartsWith(f['path'], p)]:
+ new_files.append (f)
+ else:
+ sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
+
+ self.gitStream.write("commit %s\n" % branch)
+# gitStream.write("mark :%s\n" % details["change"])
+ self.committedChanges.add(int(details["change"]))
+ committer = ""
+ if author not in self.users:
+ self.getUserMapFromPerforceServer()
+ committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
+
+ self.gitStream.write("committer %s\n" % committer)
+
+ self.gitStream.write("data <<EOT\n")
+ self.gitStream.write(details["desc"])
+ self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
+ % (','.join (branchPrefixes), details["change"]))
+ if len(details['options']) > 0:
+ self.gitStream.write(": options = %s" % details['options'])
+ self.gitStream.write("]\nEOT\n\n")
+
+ if len(parent) > 0:
+ if self.verbose:
+ print "parent %s" % parent
+ self.gitStream.write("from %s\n" % parent)
+
+ self.streamP4Files(new_files)
+ self.gitStream.write("\n")
+
+ change = int(details["change"])
+
+ if self.labels.has_key(change):
+ label = self.labels[change]
+ labelDetails = label[0]
+ labelRevisions = label[1]
+ if self.verbose:
+ print "Change %s is labelled %s" % (change, labelDetails)
+
+ files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
+ for p in branchPrefixes])
+
+ if len(files) == len(labelRevisions):
+
+ cleanedFiles = {}
+ for info in files:
+ if info["action"] in self.delete_actions:
+ continue
+ cleanedFiles[info["depotFile"]] = info["rev"]
+
+ if cleanedFiles == labelRevisions:
+ self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
+
+ else:
+ if not self.silent:
+ print ("Tag %s does not match with change %s: files do not match."
+ % (labelDetails["label"], change))
+
+ else:
+ if not self.silent:
+ print ("Tag %s does not match with change %s: file count is different."
+ % (labelDetails["label"], change))
+
+ # Build a dictionary of changelists and labels, for "detect-labels" option.
+ def getLabels(self):
+ self.labels = {}
+
+ l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
+ if len(l) > 0 and not self.silent:
+ print "Finding files belonging to labels in %s" % `self.depotPaths`
+
+ for output in l:
+ label = output["label"]
+ revisions = {}
+ newestChange = 0
+ if self.verbose:
+ print "Querying files for label %s" % label
+ for file in p4CmdList(["files"] +
+ ["%s...@%s" % (p, label)
+ for p in self.depotPaths]):
+ revisions[file["depotFile"]] = file["rev"]
+ change = int(file["change"])
+ if change > newestChange:
+ newestChange = change
+
+ self.labels[newestChange] = [output, revisions]
+
+ if self.verbose:
+ print "Label changes: %s" % self.labels.keys()
+
+ # Import p4 labels as git tags. A direct mapping does not
+ # exist, so assume that if all the files are at the same revision
+ # then we can use that, or it's something more complicated we should
+ # just ignore.
+ def importP4Labels(self, stream, p4Labels):
+ if verbose:
+ print "import p4 labels: " + ' '.join(p4Labels)
+
+ ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
+ validLabelRegexp = gitConfig("git-p4.labelImportRegexp")
+ if len(validLabelRegexp) == 0:
+ validLabelRegexp = defaultLabelRegexp
+ m = re.compile(validLabelRegexp)
+
+ for name in p4Labels:
+ commitFound = False
+
+ if not m.match(name):
+ if verbose:
+ print "label %s does not match regexp %s" % (name,validLabelRegexp)
+ continue
+
+ if name in ignoredP4Labels:
+ continue
+
+ labelDetails = p4CmdList(['label', "-o", name])[0]
+
+ # get the most recent changelist for each file in this label
+ change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
+ for p in self.depotPaths])
+
+ if change.has_key('change'):
+ # find the corresponding git commit; take the oldest commit
+ changelist = int(change['change'])
+ gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
+ "--reverse", ":/\[git-p4:.*change = %d\]" % changelist])
+ if len(gitCommit) == 0:
+ print "could not find git commit for changelist %d" % changelist
+ else:
+ gitCommit = gitCommit.strip()
+ commitFound = True
+ # Convert from p4 time format
+ try:
+ tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
+ except ValueError:
+ print "Could not convert label time %s" % labelDetail['Update']
+ tmwhen = 1
+
+ when = int(time.mktime(tmwhen))
+ self.streamTag(stream, name, labelDetails, gitCommit, when)
+ if verbose:
+ print "p4 label %s mapped to git commit %s" % (name, gitCommit)
+ else:
+ if verbose:
+ print "Label %s has no changelists - possibly deleted?" % name
+
+ if not commitFound:
+ # We can't import this label; don't try again as it will get very
+ # expensive repeatedly fetching all the files for labels that will
+ # never be imported. If the label is moved in the future, the
+ # ignore will need to be removed manually.
+ system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
+
+ def guessProjectName(self):
+ for p in self.depotPaths:
+ if p.endswith("/"):
+ p = p[:-1]
+ p = p[p.strip().rfind("/") + 1:]
+ if not p.endswith("/"):
+ p += "/"
+ return p
+
+ def getBranchMapping(self):
+ lostAndFoundBranches = set()
+
+ user = gitConfig("git-p4.branchUser")
+ if len(user) > 0:
+ command = "branches -u %s" % user
+ else:
+ command = "branches"
+
+ for info in p4CmdList(command):
+ details = p4Cmd(["branch", "-o", info["branch"]])
+ viewIdx = 0
+ while details.has_key("View%s" % viewIdx):
+ paths = details["View%s" % viewIdx].split(" ")
+ viewIdx = viewIdx + 1
+ # require standard //depot/foo/... //depot/bar/... mapping
+ if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
+ continue
+ source = paths[0]
+ destination = paths[1]
+ ## HACK
+ if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
+ source = source[len(self.depotPaths[0]):-4]
+ destination = destination[len(self.depotPaths[0]):-4]
+
+ if destination in self.knownBranches:
+ if not self.silent:
+ print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
+ print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
+ continue
+
+ self.knownBranches[destination] = source
+
+ lostAndFoundBranches.discard(destination)
+
+ if source not in self.knownBranches:
+ lostAndFoundBranches.add(source)
+
+ # Perforce does not strictly require branches to be defined, so we also
+ # check git config for a branch list.
+ #
+ # Example of branch definition in git config file:
+ # [git-p4]
+ # branchList=main:branchA
+ # branchList=main:branchB
+ # branchList=branchA:branchC
+ configBranches = gitConfigList("git-p4.branchList")
+ for branch in configBranches:
+ if branch:
+ (source, destination) = branch.split(":")
+ self.knownBranches[destination] = source
+
+ lostAndFoundBranches.discard(destination)
+
+ if source not in self.knownBranches:
+ lostAndFoundBranches.add(source)
+
+
+ for branch in lostAndFoundBranches:
+ self.knownBranches[branch] = branch
+
+ def getBranchMappingFromGitBranches(self):
+ branches = p4BranchesInGit(self.importIntoRemotes)
+ for branch in branches.keys():
+ if branch == "master":
+ branch = "main"
+ else:
+ branch = branch[len(self.projectName):]
+ self.knownBranches[branch] = branch
+
+ def listExistingP4GitBranches(self):
+ # branches holds mapping from name to commit
+ branches = p4BranchesInGit(self.importIntoRemotes)
+ self.p4BranchesInGit = branches.keys()
+ for branch in branches.keys():
+ self.initialParents[self.refPrefix + branch] = branches[branch]
+
+ def updateOptionDict(self, d):
+ option_keys = {}
+ if self.keepRepoPath:
+ option_keys['keepRepoPath'] = 1
+
+ d["options"] = ' '.join(sorted(option_keys.keys()))
+
+ def readOptions(self, d):
+ self.keepRepoPath = (d.has_key('options')
+ and ('keepRepoPath' in d['options']))
+
+ def gitRefForBranch(self, branch):
+ if branch == "main":
+ return self.refPrefix + "master"
+
+ if len(branch) <= 0:
+ return branch
+
+ return self.refPrefix + self.projectName + branch
+
+ def gitCommitByP4Change(self, ref, change):
+ if self.verbose:
+ print "looking in ref " + ref + " for change %s using bisect..." % change
+
+ earliestCommit = ""
+ latestCommit = parseRevision(ref)
+
+ while True:
+ if self.verbose:
+ print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
+ next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
+ if len(next) == 0:
+ if self.verbose:
+ print "argh"
+ return ""
+ log = extractLogMessageFromGitCommit(next)
+ settings = extractSettingsGitLog(log)
+ currentChange = int(settings['change'])
+ if self.verbose:
+ print "current change %s" % currentChange
+
+ if currentChange == change:
+ if self.verbose:
+ print "found %s" % next
+ return next
+
+ if currentChange < change:
+ earliestCommit = "^%s" % next
+ else:
+ latestCommit = "%s" % next
+
+ return ""
+
+ def importNewBranch(self, branch, maxChange):
+ # make fast-import flush all changes to disk and update the refs using the checkpoint
+ # command so that we can try to find the branch parent in the git history
+ self.gitStream.write("checkpoint\n\n");
+ self.gitStream.flush();
+ branchPrefix = self.depotPaths[0] + branch + "/"
+ range = "@1,%s" % maxChange
+ #print "prefix" + branchPrefix
+ changes = p4ChangesForPaths([branchPrefix], range)
+ if len(changes) <= 0:
+ return False
+ firstChange = changes[0]
+ #print "first change in branch: %s" % firstChange
+ sourceBranch = self.knownBranches[branch]
+ sourceDepotPath = self.depotPaths[0] + sourceBranch
+ sourceRef = self.gitRefForBranch(sourceBranch)
+ #print "source " + sourceBranch
+
+ branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
+ #print "branch parent: %s" % branchParentChange
+ gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
+ if len(gitParent) > 0:
+ self.initialParents[self.gitRefForBranch(branch)] = gitParent
+ #print "parent git commit: %s" % gitParent
+
+ self.importChanges(changes)
+ return True
+
+ def searchParent(self, parent, branch, target):
+ parentFound = False
+ for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
+ blob = blob.strip()
+ if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
+ parentFound = True
+ if self.verbose:
+ print "Found parent of %s in commit %s" % (branch, blob)
+ break
+ if parentFound:
+ return blob
+ else:
+ return None
+
+ def importChanges(self, changes):
+ cnt = 1
+ for change in changes:
+ description = p4Cmd(["describe", str(change)])
+ self.updateOptionDict(description)
+
+ if not self.silent:
+ sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
+ sys.stdout.flush()
+ cnt = cnt + 1
+
+ try:
+ if self.detectBranches:
+ branches = self.splitFilesIntoBranches(description)
+ for branch in branches.keys():
+ ## HACK --hwn
+ branchPrefix = self.depotPaths[0] + branch + "/"
+
+ parent = ""
+
+ filesForCommit = branches[branch]
+
+ if self.verbose:
+ print "branch is %s" % branch
+
+ self.updatedBranches.add(branch)
+
+ if branch not in self.createdBranches:
+ self.createdBranches.add(branch)
+ parent = self.knownBranches[branch]
+ if parent == branch:
+ parent = ""
+ else:
+ fullBranch = self.projectName + branch
+ if fullBranch not in self.p4BranchesInGit:
+ if not self.silent:
+ print("\n Importing new branch %s" % fullBranch);
+ if self.importNewBranch(branch, change - 1):
+ parent = ""
+ self.p4BranchesInGit.append(fullBranch)
+ if not self.silent:
+ print("\n Resuming with change %s" % change);
+
+ if self.verbose:
+ print "parent determined through known branches: %s" % parent
+
+ branch = self.gitRefForBranch(branch)
+ parent = self.gitRefForBranch(parent)
+
+ if self.verbose:
+ print "looking for initial parent for %s; current parent is %s" % (branch, parent)
+
+ if len(parent) == 0 and branch in self.initialParents:
+ parent = self.initialParents[branch]
+ del self.initialParents[branch]
+
+ blob = None
+ if len(parent) > 0:
+ tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
+ if self.verbose:
+ print "Creating temporary branch: " + tempBranch
+ self.commit(description, filesForCommit, tempBranch, [branchPrefix])
+ self.tempBranches.append(tempBranch)
+ self.checkpoint()
+ blob = self.searchParent(parent, branch, tempBranch)
+ if blob:
+ self.commit(description, filesForCommit, branch, [branchPrefix], blob)
+ else:
+ if self.verbose:
+ print "Parent of %s not found. Committing into head of %s" % (branch, parent)
+ self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+ else:
+ files = self.extractFilesFromCommit(description)
+ self.commit(description, files, self.branch, self.depotPaths,
+ self.initialParent)
+ self.initialParent = ""
+ except IOError:
+ print self.gitError.read()
+ sys.exit(1)
+
+ def importHeadRevision(self, revision):
+ print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
+
+ details = {}
+ details["user"] = "git perforce import user"
+ details["desc"] = ("Initial import of %s from the state at revision %s\n"
+ % (' '.join(self.depotPaths), revision))
+ details["change"] = revision
+ newestRevision = 0
+
+ fileCnt = 0
+ fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+
+ for info in p4CmdList(["files"] + fileArgs):
+
+ if 'code' in info and info['code'] == 'error':
+ sys.stderr.write("p4 returned an error: %s\n"
+ % info['data'])
+ if info['data'].find("must refer to client") >= 0:
+ sys.stderr.write("This particular p4 error is misleading.\n")
+ sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
+ sys.exit(1)
+ if 'p4ExitCode' in info:
+ sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
+ sys.exit(1)
+
+
+ change = int(info["change"])
+ if change > newestRevision:
+ newestRevision = change
+
+ if info["action"] in self.delete_actions:
+ # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
+ #fileCnt = fileCnt + 1
+ continue
+
+ for prop in ["depotFile", "rev", "action", "type" ]:
+ details["%s%s" % (prop, fileCnt)] = info[prop]
+
+ fileCnt = fileCnt + 1
+
+ details["change"] = newestRevision
+
+ # Use time from top-most change so that all git p4 clones of
+ # the same p4 repo have the same commit SHA1s.
+ res = p4CmdList("describe -s %d" % newestRevision)
+ newestTime = None
+ for r in res:
+ if r.has_key('time'):
+ newestTime = int(r['time'])
+ if newestTime is None:
+ die("\"describe -s\" on newest change %d did not give a time")
+ details["time"] = newestTime
+
+ self.updateOptionDict(details)
+ try:
+ self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
+ except IOError:
+ print "IO error with git fast-import. Is your git version recent enough?"
+ print self.gitError.read()
+
+
+ def run(self, args):
+ self.depotPaths = []
+ self.changeRange = ""
+ self.initialParent = ""
+ self.previousDepotPaths = []
+
+ # map from branch depot path to parent branch
+ self.knownBranches = {}
+ self.initialParents = {}
+ self.hasOrigin = originP4BranchesExist()
+ if not self.syncWithOrigin:
+ self.hasOrigin = False
+
+ if self.importIntoRemotes:
+ self.refPrefix = "refs/remotes/p4/"
+ else:
+ self.refPrefix = "refs/heads/p4/"
+
+ if self.syncWithOrigin and self.hasOrigin:
+ if not self.silent:
+ print "Syncing with origin first by calling git fetch origin"
+ system("git fetch origin")
+
+ if len(self.branch) == 0:
+ self.branch = self.refPrefix + "master"
+ if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
+ system("git update-ref %s refs/heads/p4" % self.branch)
+ system("git branch -D p4");
+ # create it /after/ importing, when master exists
+ if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
+ system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+
+ # accept either the command-line option, or the configuration variable
+ if self.useClientSpec:
+ # will use this after clone to set the variable
+ self.useClientSpec_from_options = True
+ else:
+ if gitConfig("git-p4.useclientspec", "--bool") == "true":
+ self.useClientSpec = True
+ if self.useClientSpec:
+ self.clientSpecDirs = getClientSpec()
+
+ # TODO: should always look at previous commits,
+ # merge with previous imports, if possible.
+ if args == []:
+ if self.hasOrigin:
+ createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
+ self.listExistingP4GitBranches()
+
+ if len(self.p4BranchesInGit) > 1:
+ if not self.silent:
+ print "Importing from/into multiple branches"
+ self.detectBranches = True
+
+ if self.verbose:
+ print "branches: %s" % self.p4BranchesInGit
+
+ p4Change = 0
+ for branch in self.p4BranchesInGit:
+ logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
+
+ settings = extractSettingsGitLog(logMsg)
+
+ self.readOptions(settings)
+ if (settings.has_key('depot-paths')
+ and settings.has_key ('change')):
+ change = int(settings['change']) + 1
+ p4Change = max(p4Change, change)
+
+ depotPaths = sorted(settings['depot-paths'])
+ if self.previousDepotPaths == []:
+ self.previousDepotPaths = depotPaths
+ else:
+ paths = []
+ for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
+ prev_list = prev.split("/")
+ cur_list = cur.split("/")
+ for i in range(0, min(len(cur_list), len(prev_list))):
+ if cur_list[i] <> prev_list[i]:
+ i = i - 1
+ break
+
+ paths.append ("/".join(cur_list[:i + 1]))
+
+ self.previousDepotPaths = paths
+
+ if p4Change > 0:
+ self.depotPaths = sorted(self.previousDepotPaths)
+ self.changeRange = "@%s,#head" % p4Change
+ if not self.detectBranches:
+ self.initialParent = parseRevision(self.branch)
+ if not self.silent and not self.detectBranches:
+ print "Performing incremental import into %s git branch" % self.branch
+
+ if not self.branch.startswith("refs/"):
+ self.branch = "refs/heads/" + self.branch
+
+ if len(args) == 0 and self.depotPaths:
+ if not self.silent:
+ print "Depot paths: %s" % ' '.join(self.depotPaths)
+ else:
+ if self.depotPaths and self.depotPaths != args:
+ print ("previous import used depot path %s and now %s was specified. "
+ "This doesn't work!" % (' '.join (self.depotPaths),
+ ' '.join (args)))
+ sys.exit(1)
+
+ self.depotPaths = sorted(args)
+
+ revision = ""
+ self.users = {}
+
+ # Make sure no revision specifiers are used when --changesfile
+ # is specified.
+ bad_changesfile = False
+ if len(self.changesFile) > 0:
+ for p in self.depotPaths:
+ if p.find("@") >= 0 or p.find("#") >= 0:
+ bad_changesfile = True
+ break
+ if bad_changesfile:
+ die("Option --changesfile is incompatible with revision specifiers")
+
+ newPaths = []
+ for p in self.depotPaths:
+ if p.find("@") != -1:
+ atIdx = p.index("@")
+ self.changeRange = p[atIdx:]
+ if self.changeRange == "@all":
+ self.changeRange = ""
+ elif ',' not in self.changeRange:
+ revision = self.changeRange
+ self.changeRange = ""
+ p = p[:atIdx]
+ elif p.find("#") != -1:
+ hashIdx = p.index("#")
+ revision = p[hashIdx:]
+ p = p[:hashIdx]
+ elif self.previousDepotPaths == []:
+ # pay attention to changesfile, if given, else import
+ # the entire p4 tree at the head revision
+ if len(self.changesFile) == 0:
+ revision = "#head"
+
+ p = re.sub ("\.\.\.$", "", p)
+ if not p.endswith("/"):
+ p += "/"
+
+ newPaths.append(p)
+
+ self.depotPaths = newPaths
+
+ self.loadUserMapFromCache()
+ self.labels = {}
+ if self.detectLabels:
+ self.getLabels();
+
+ if self.detectBranches:
+ ## FIXME - what's a P4 projectName ?
+ self.projectName = self.guessProjectName()
+
+ if self.hasOrigin:
+ self.getBranchMappingFromGitBranches()
+ else:
+ self.getBranchMapping()
+ if self.verbose:
+ print "p4-git branches: %s" % self.p4BranchesInGit
+ print "initial parents: %s" % self.initialParents
+ for b in self.p4BranchesInGit:
+ if b != "master":
+
+ ## FIXME
+ b = b[len(self.projectName):]
+ self.createdBranches.add(b)
+
+ self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
+
+ importProcess = subprocess.Popen(["git", "fast-import"],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE);
+ self.gitOutput = importProcess.stdout
+ self.gitStream = importProcess.stdin
+ self.gitError = importProcess.stderr
+
+ if revision:
+ self.importHeadRevision(revision)
+ else:
+ changes = []
+
+ if len(self.changesFile) > 0:
+ output = open(self.changesFile).readlines()
+ changeSet = set()
+ for line in output:
+ changeSet.add(int(line))
+
+ for change in changeSet:
+ changes.append(change)
+
+ changes.sort()
+ else:
+ # catch "git p4 sync" with no new branches, in a repo that
+ # does not have any existing p4 branches
+ if len(args) == 0 and not self.p4BranchesInGit:
+ die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
+ if self.verbose:
+ print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
+ self.changeRange)
+ changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
+
+ if len(self.maxChanges) > 0:
+ changes = changes[:min(int(self.maxChanges), len(changes))]
+
+ if len(changes) == 0:
+ if not self.silent:
+ print "No changes to import!"
+ else:
+ if not self.silent and not self.detectBranches:
+ print "Import destination: %s" % self.branch
+
+ self.updatedBranches = set()
+
+ self.importChanges(changes)
+
+ if not self.silent:
+ print ""
+ if len(self.updatedBranches) > 0:
+ sys.stdout.write("Updated branches: ")
+ for b in self.updatedBranches:
+ sys.stdout.write("%s " % b)
+ sys.stdout.write("\n")
+
+ if gitConfig("git-p4.importLabels", "--bool") == "true":
+ self.importLabels = True
+
+ if self.importLabels:
+ p4Labels = getP4Labels(self.depotPaths)
+ gitTags = getGitTags()
+
+ missingP4Labels = p4Labels - gitTags
+ self.importP4Labels(self.gitStream, missingP4Labels)
+
+ self.gitStream.close()
+ if importProcess.wait() != 0:
+ die("fast-import failed: %s" % self.gitError.read())
+ self.gitOutput.close()
+ self.gitError.close()
+
+ # Cleanup temporary branches created during import
+ if self.tempBranches != []:
+ for branch in self.tempBranches:
+ read_pipe("git update-ref -d %s" % branch)
+ os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+
+ return True
+
+class P4Rebase(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
+ ]
+ self.importLabels = False
+ self.description = ("Fetches the latest revision from perforce and "
+ + "rebases the current work (branch) against it")
+
+ def run(self, args):
+ sync = P4Sync()
+ sync.importLabels = self.importLabels
+ sync.run([])
+
+ return self.rebase()
+
+ def rebase(self):
+ if os.system("git update-index --refresh") != 0:
+ die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
+ if len(read_pipe("git diff-index HEAD --")) > 0:
+ die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+
+ [upstream, settings] = findUpstreamBranchPoint()
+ if len(upstream) == 0:
+ die("Cannot find upstream branchpoint for rebase")
+
+ # the branchpoint may be p4/foo~3, so strip off the parent
+ upstream = re.sub("~[0-9]+$", "", upstream)
+
+ print "Rebasing the current branch onto %s" % upstream
+ oldHead = read_pipe("git rev-parse HEAD").strip()
+ system("git rebase %s" % upstream)
+ system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
+ return True
+
+class P4Clone(P4Sync):
+ def __init__(self):
+ P4Sync.__init__(self)
+ self.description = "Creates a new git repository and imports from Perforce into it"
+ self.usage = "usage: %prog [options] //depot/path[@revRange]"
+ self.options += [
+ optparse.make_option("--destination", dest="cloneDestination",
+ action='store', default=None,
+ help="where to leave result of the clone"),
+ optparse.make_option("-/", dest="cloneExclude",
+ action="append", type="string",
+ help="exclude depot path"),
+ optparse.make_option("--bare", dest="cloneBare",
+ action="store_true", default=False),
+ ]
+ self.cloneDestination = None
+ self.needsGit = False
+ self.cloneBare = False
+
+ # This is required for the "append" cloneExclude action
+ def ensure_value(self, attr, value):
+ if not hasattr(self, attr) or getattr(self, attr) is None:
+ setattr(self, attr, value)
+ return getattr(self, attr)
+
+ def defaultDestination(self, args):
+ ## TODO: use common prefix of args?
+ depotPath = args[0]
+ depotDir = re.sub("(@[^@]*)$", "", depotPath)
+ depotDir = re.sub("(#[^#]*)$", "", depotDir)
+ depotDir = re.sub(r"\.\.\.$", "", depotDir)
+ depotDir = re.sub(r"/$", "", depotDir)
+ return os.path.split(depotDir)[1]
+
+ def run(self, args):
+ if len(args) < 1:
+ return False
+
+ if self.keepRepoPath and not self.cloneDestination:
+ sys.stderr.write("Must specify destination for --keep-path\n")
+ sys.exit(1)
+
+ depotPaths = args
+
+ if not self.cloneDestination and len(depotPaths) > 1:
+ self.cloneDestination = depotPaths[-1]
+ depotPaths = depotPaths[:-1]
+
+ self.cloneExclude = ["/"+p for p in self.cloneExclude]
+ for p in depotPaths:
+ if not p.startswith("//"):
+ return False
+
+ if not self.cloneDestination:
+ self.cloneDestination = self.defaultDestination(args)
+
+ print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+
+ if not os.path.exists(self.cloneDestination):
+ os.makedirs(self.cloneDestination)
+ chdir(self.cloneDestination)
+
+ init_cmd = [ "git", "init" ]
+ if self.cloneBare:
+ init_cmd.append("--bare")
+ subprocess.check_call(init_cmd)
+
+ if not P4Sync.run(self, depotPaths):
+ return False
+ if self.branch != "master":
+ if self.importIntoRemotes:
+ masterbranch = "refs/remotes/p4/master"
+ else:
+ masterbranch = "refs/heads/p4/master"
+ if gitBranchExists(masterbranch):
+ system("git branch master %s" % masterbranch)
+ if not self.cloneBare:
+ system("git checkout -f")
+ else:
+ print "Could not detect main branch. No checkout/master branch created."
+
+ # auto-set this variable if invoked with --use-client-spec
+ if self.useClientSpec_from_options:
+ system("git config --bool git-p4.useclientspec true")
+
+ return True
+
+class P4Branches(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [ ]
+ self.description = ("Shows the git branches that hold imports and their "
+ + "corresponding perforce depot paths")
+ self.verbose = False
+
+ def run(self, args):
+ if originP4BranchesExist():
+ createOrUpdateBranchesFromOrigin()
+
+ cmdline = "git rev-parse --symbolic "
+ cmdline += " --remotes"
+
+ for line in read_pipe_lines(cmdline):
+ line = line.strip()
+
+ if not line.startswith('p4/') or line == "p4/HEAD":
+ continue
+ branch = line
+
+ log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
+ settings = extractSettingsGitLog(log)
+
+ print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
+ return True
+
+class HelpFormatter(optparse.IndentedHelpFormatter):
+ def __init__(self):
+ optparse.IndentedHelpFormatter.__init__(self)
+
+ def format_description(self, description):
+ if description:
+ return description + "\n"
+ else:
+ return ""
+
+def printUsage(commands):
+ print "usage: %s <command> [options]" % sys.argv[0]
+ print ""
+ print "valid commands: %s" % ", ".join(commands)
+ print ""
+ print "Try %s <command> --help for command specific help." % sys.argv[0]
+ print ""
+
+commands = {
+ "debug" : P4Debug,
+ "submit" : P4Submit,
+ "commit" : P4Submit,
+ "sync" : P4Sync,
+ "rebase" : P4Rebase,
+ "clone" : P4Clone,
+ "rollback" : P4RollBack,
+ "branches" : P4Branches
+}
+
+
+def main():
+ if len(sys.argv[1:]) == 0:
+ printUsage(commands.keys())
+ sys.exit(2)
+
+ cmd = ""
+ cmdName = sys.argv[1]
+ try:
+ klass = commands[cmdName]
+ cmd = klass()
+ except KeyError:
+ print "unknown command %s" % cmdName
+ print ""
+ printUsage(commands.keys())
+ sys.exit(2)
+
+ options = cmd.options
+ cmd.gitdir = os.environ.get("GIT_DIR", None)
+
+ args = sys.argv[2:]
+
+ options.append(optparse.make_option("--verbose", dest="verbose", action="store_true"))
+ if cmd.needsGit:
+ options.append(optparse.make_option("--git-dir", dest="gitdir"))
+
+ parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
+ options,
+ description = cmd.description,
+ formatter = HelpFormatter())
+
+ (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+ global verbose
+ verbose = cmd.verbose
+ if cmd.needsGit:
+ if cmd.gitdir == None:
+ cmd.gitdir = os.path.abspath(".git")
+ if not isValidGitDir(cmd.gitdir):
+ cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
+ if os.path.exists(cmd.gitdir):
+ cdup = read_pipe("git rev-parse --show-cdup").strip()
+ if len(cdup) > 0:
+ chdir(cdup);
+
+ if not isValidGitDir(cmd.gitdir):
+ if isValidGitDir(cmd.gitdir + "/.git"):
+ cmd.gitdir += "/.git"
+ else:
+ die("fatal: cannot locate git repository at %s" % cmd.gitdir)
+
+ os.environ["GIT_DIR"] = cmd.gitdir
+
+ if not cmd.run(args):
+ parser.print_help()
+ sys.exit(2)
+
+
+if __name__ == '__main__':
+ main()
test -n "$rebase_root" && root_flag=--root
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
- --src-prefix=a/ --dst-prefix=b/ \
- --no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+ # we have to do this the hard way. git format-patch completely squashes
+ # empty commits and even if it didn't the format doesn't really lend
+ # itself well to recording empty patches. fortunately, cherry-pick
+ # makes this easy
+ git cherry-pick --allow-empty "$revisions"
+else
+ git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+ --src-prefix=a/ --dst-prefix=b/ \
+ --no-renames $root_flag "$revisions" |
+ git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
ret=$?
test 0 != $ret -a -d "$state_dir" && write_basic_state
exit $ret
sane_grep '^[^#]' "$1" >/dev/null
}
+is_empty_commit() {
+ tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
+ die "$1: not a commit that can be picked")
+ ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
+ ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+ test "$tree" = "$ptree"
+}
+
# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
# GIT_AUTHOR_DATE exported from the current environment.
do_with_author () {
pick_one () {
ff=--ff
+
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
case "$force_rebase" in '') ;; ?*) ff= ;; esac
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+ if is_empty_commit "$sha1"
+ then
+ empty_args="--allow-empty"
+ fi
+
test -d "$rewritten" &&
pick_one_preserving_merges "$@" && return
- output git cherry-pick $ff "$@"
+ output git cherry-pick $empty_args $ff "$@"
}
pick_one_preserving_merges () {
sed -n "s/^>//p" |
while read -r shortsha1 rest
do
+
+ if test -z "$keep_empty" && is_empty_commit $shortsha1
+ then
+ comment_out="# "
+ else
+ comment_out=
+ fi
+
if test t != "$preserve_merges"
then
- printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
else
sha1=$(git rev-parse $shortsha1)
if test -z "$rebase_root"
if test f = "$preserve"
then
touch "$rewritten"/$sha1
- printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
fi
fi
done
#
EOF
+if test -z "$keep_empty"
+then
+ echo "# Note that empty commits are commented out" >>"$todo"
+fi
+
+
has_action "$todo" ||
die_abort "Nothing to do"
no-ff! cherry-pick all commits, even if unchanged
m,merge! use merging strategies to rebase
i,interactive! let the user edit the list of commits to rebase
+k,keep-empty preserve empty commits during rebase
f,force-rebase! force rebase even if branch is up to date
X,strategy-option=! pass the argument through to the merge strategy
stat! display a diffstat of what changed upstream
action=
preserve_merges=
autosquash=
+keep_empty=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
read_basic_state () {
-i)
interactive_rebase=explicit
;;
+ -k)
+ keep_empty=yes
+ ;;
-p)
preserve_merges=t
test -z "$interactive_rebase" && interactive_rebase=implied
find () {
/usr/bin/find "$@"
}
+ # git sees Windows-style pwd
+ pwd () {
+ builtin pwd -W
+ }
is_absolute_path () {
case "$1" in
[/\\]* | [A-Za-z]:*)
module_name()
{
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
+ sm_path="$1"
re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
test -z "$name" &&
- die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
+ die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
echo "$name"
}
#
module_clone()
{
- path=$1
+ sm_path=$1
url=$2
reference="$3"
quiet=
gitdir=
gitdir_base=
- name=$(module_name "$path" 2>/dev/null)
- test -n "$name" || name="$path"
+ name=$(module_name "$sm_path" 2>/dev/null)
+ test -n "$name" || name="$sm_path"
base_name=$(dirname "$name")
gitdir=$(git rev-parse --git-dir)
if test -d "$gitdir"
then
- mkdir -p "$path"
+ mkdir -p "$sm_path"
rm -f "$gitdir/index"
else
mkdir -p "$gitdir_base"
git clone $quiet -n ${reference:+"$reference"} \
- --separate-git-dir "$gitdir" "$url" "$path" ||
- die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+ --separate-git-dir "$gitdir" "$url" "$sm_path" ||
+ die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
fi
a=$(cd "$gitdir" && pwd)/
- b=$(cd "$path" && pwd)/
+ b=$(cd "$sm_path" && pwd)/
# normalize Windows-style absolute paths to POSIX-style absolute paths
case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac
case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac
# Turn each leading "*/" component into "../"
rel=$(echo $b | sed -e 's|[^/][^/]*|..|g')
- echo "gitdir: $rel/$a" >"$path/.git"
+ echo "gitdir: $rel/$a" >"$sm_path/.git"
rel=$(echo $a | sed -e 's|[^/][^/]*|..|g')
- (clear_local_git_env; cd "$path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
+ (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
}
#
done
repo=$1
- path=$2
+ sm_path=$2
- if test -z "$path"; then
- path=$(echo "$repo" |
+ if test -z "$sm_path"; then
+ sm_path=$(echo "$repo" |
sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
fi
- if test -z "$repo" -o -z "$path"; then
+ if test -z "$repo" -o -z "$sm_path"; then
usage
fi
# normalize path:
# multiple //; leading ./; /./; /../; trailing /
- path=$(printf '%s/\n' "$path" |
+ sm_path=$(printf '%s/\n' "$sm_path" |
sed -e '
s|//*|/|g
s|^\(\./\)*||
tstart
s|/*$||
')
- git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
- die "$(eval_gettext "'\$path' already exists in the index")"
+ git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
+ die "$(eval_gettext "'\$sm_path' already exists in the index")"
- if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
+ if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
then
eval_gettextln "The following path is ignored by one of your .gitignore files:
-\$path
+\$sm_path
Use -f if you really want to add it." >&2
exit 1
fi
# perhaps the path exists and is already a git repo, else clone it
- if test -e "$path"
+ if test -e "$sm_path"
then
- if test -d "$path"/.git -o -f "$path"/.git
+ if test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
- eval_gettextln "Adding existing repo at '\$path' to the index"
+ eval_gettextln "Adding existing repo at '\$sm_path' to the index"
else
- die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
+ die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
fi
else
- module_clone "$path" "$realrepo" "$reference" || exit
+ module_clone "$sm_path" "$realrepo" "$reference" || exit
(
clear_local_git_env
- cd "$path" &&
+ cd "$sm_path" &&
# ash fails to wordsplit ${branch:+-b "$branch"...}
case "$branch" in
'') git checkout -f -q ;;
?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
esac
- ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")"
+ ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
fi
- git config submodule."$path".url "$realrepo"
+ git config submodule."$sm_path".url "$realrepo"
- git add $force "$path" ||
- die "$(eval_gettext "Failed to add submodule '\$path'")"
+ git add $force "$sm_path" ||
+ die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
- git config -f .gitmodules submodule."$path".path "$path" &&
- git config -f .gitmodules submodule."$path".url "$repo" &&
+ git config -f .gitmodules submodule."$sm_path".path "$sm_path" &&
+ git config -f .gitmodules submodule."$sm_path".url "$repo" &&
git add --force .gitmodules ||
- die "$(eval_gettext "Failed to register submodule '\$path'")"
+ die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
}
#
exec 3<&0
module_list |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- if test -e "$path"/.git
+ if test -e "$sm_path"/.git
then
- say "$(eval_gettext "Entering '\$prefix\$path'")"
- name=$(module_name "$path")
+ say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+ name=$(module_name "$sm_path")
(
- prefix="$prefix$path/"
+ prefix="$prefix$sm_path/"
clear_local_git_env
- cd "$path" &&
+ # we make $path available to scripts ...
+ path=$sm_path
+ cd "$sm_path" &&
eval "$@" &&
if test -n "$recursive"
then
cmd_foreach "--recursive" "$@"
fi
) <&3 3<&- ||
- die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")"
+ die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
fi
done
}
done
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
# Skip already registered paths
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
if test -z "$(git config "submodule.$name.url")"
then
url=$(git config -f .gitmodules submodule."$name".url)
test -z "$url" &&
- die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")"
+ die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
# Possibly a url relative to parent
case "$url" in
;;
esac
git config submodule."$name".url "$url" ||
- die "$(eval_gettext "Failed to register url for submodule path '\$path'")"
+ die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
fi
# Copy "update" setting when it is not set yet
test -z "$upd" ||
test -n "$(git config submodule."$name".update)" ||
git config submodule."$name".update "$upd" ||
- die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")"
+ die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
- say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")"
+ say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
done
}
cloned_modules=
module_list "$@" | {
err=
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
if test "$stage" = U
then
- echo >&2 "Skipping unmerged submodule $path"
+ echo >&2 "Skipping unmerged submodule $sm_path"
continue
fi
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
if ! test -z "$update"
then
if test "$update_module" = "none"
then
- echo "Skipping submodule '$path'"
+ echo "Skipping submodule '$sm_path'"
continue
fi
# Only mention uninitialized submodules when its
# path have been specified
test "$#" != "0" &&
- say "$(eval_gettext "Submodule path '\$path' not initialized
+ say "$(eval_gettext "Submodule path '\$sm_path' not initialized
Maybe you want to use 'update --init'?")"
continue
fi
- if ! test -d "$path"/.git -o -f "$path"/.git
+ if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
- module_clone "$path" "$url" "$reference"|| exit
+ module_clone "$sm_path" "$url" "$reference"|| exit
cloned_modules="$cloned_modules;$name"
subsha1=
else
- subsha1=$(clear_local_git_env; cd "$path" &&
+ subsha1=$(clear_local_git_env; cd "$sm_path" &&
git rev-parse --verify HEAD) ||
- die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
+ die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi
if test "$subsha1" != "$sha1"
then
# Run fetch only if $sha1 isn't present or it
# is not reachable from a ref.
- (clear_local_git_env; cd "$path" &&
+ (clear_local_git_env; cd "$sm_path" &&
( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
test -z "$rev") || git-fetch)) ||
- die "$(eval_gettext "Unable to fetch in submodule path '\$path'")"
+ die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
fi
# Is this something we just cloned?
case "$update_module" in
rebase)
command="git rebase"
- die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")"
- say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
must_die_on_failure=yes
;;
merge)
command="git merge"
- die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")"
- say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': merged in '\$sha1'")"
must_die_on_failure=yes
;;
*)
command="git checkout $subforce -q"
- die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")"
- say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': checked out '\$sha1'")"
;;
esac
- if (clear_local_git_env; cd "$path" && $command "$sha1")
+ if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
then
say "$say_msg"
elif test -n "$must_die_on_failure"
if test -n "$recursive"
then
- (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags")
+ (clear_local_git_env; cd "$sm_path" && eval cmd_update "$orig_flags")
res=$?
if test $res -gt 0
then
- die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+ die_msg="$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
if test $res -eq 1
then
err="${err};$die_msg"
done
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
- displaypath="$prefix$path"
+ displaypath="$prefix$sm_path"
if test "$stage" = U
then
say "U$sha1 $displaypath"
continue
fi
- if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
+ if test -z "$url" || ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
say "-$sha1 $displaypath"
continue;
fi
- set_name_rev "$path" "$sha1"
- if git diff-files --ignore-submodules=dirty --quiet -- "$path"
+ set_name_rev "$sm_path" "$sha1"
+ if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
then
say " $sha1 $displaypath$revname"
else
if test -z "$cached"
then
- sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD)
- set_name_rev "$path" "$sha1"
+ sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
+ set_name_rev "$sm_path" "$sha1"
fi
say "+$sha1 $displaypath$revname"
fi
(
prefix="$displaypath/"
clear_local_git_env
- cd "$path" &&
+ cd "$sm_path" &&
eval cmd_status "$orig_args"
) ||
- die "$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+ die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
fi
done
}
done
cd_to_toplevel
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- name=$(module_name "$path")
+ name=$(module_name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url)
# Possibly a url relative to parent
say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
git config submodule."$name".url "$url"
- if test -e "$path"/.git
+ if test -e "$sm_path"/.git
then
(
clear_local_git_env
- cd "$path"
+ cd "$sm_path"
remote=$(get_default_remote)
git config remote."$remote".url "$url"
)
$| = 1; # unbuffer STDOUT
sub fatal (@) { print STDERR "@_\n"; exit 1 }
+
+# All SVN commands do it. Otherwise we may die on SIGPIPE when the remote
+# repository decides to close the connection which we expect to be kept alive.
+$SIG{PIPE} = 'IGNORE';
+
+# Given a dot separated version number, "subtract" it from
+# the SVN::Core::VERSION; non-negaitive return means the SVN::Core
+# is at least at the version the caller asked for.
+sub compare_svn_version {
+ my (@ours) = split(/\./, $SVN::Core::VERSION);
+ my (@theirs) = split(/\./, $_[0]);
+ my ($i, $diff);
+
+ for ($i = 0; $i < @ours && $i < @theirs; $i++) {
+ $diff = $ours[$i] - $theirs[$i];
+ return $diff if ($diff);
+ }
+ return 1 if ($i < @ours);
+ return -1 if ($i < @theirs);
+ return 0;
+}
+
sub _req_svn {
require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
require SVN::Ra;
require SVN::Delta;
- if ($SVN::Core::VERSION lt '1.1.0') {
+ if (::compare_svn_version('1.1.0') < 0) {
fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
}
}
$_message, $_file, $_branch_dest,
$_template, $_shared,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
- $_merge, $_strategy, $_dry_run, $_local,
+ $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
$_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
$Git::SVN::_follow_parent = 1;
'local|l' => \$_local,
'fetch-all|all' => \$_fetch_all,
'dry-run|n' => \$_dry_run,
+ 'preserve-merges|p' => \$_preserve_merges,
%fc_opts } ],
'commit-diff' => [ \&cmd_commit_diff,
'Commit a diff between two trees',
}
::_req_svn();
$result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
- ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
+ (::compare_svn_version('1.5.4') <= 0 || $file_type ne "dir");
$result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
$result .= "Node Kind: " .
push @cmd, '-v' if $_verbose;
push @cmd, qw/--merge/ if $_merge;
push @cmd, "--strategy=$_strategy" if $_strategy;
+ push @cmd, "--preserve-merges" if $_preserve_merges;
@cmd;
}
use Time::Local;
use Memoize; # core since 5.8.0, Jul 2002
use Memoize::Storable;
+use POSIX qw(:signal_h);
my ($_gc_nr, $_gc_period);
length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
my $db = $self->map_path($uuid);
my $db_lock = "$db.lock";
- my $sig;
+ my $sigmask;
$update_ref ||= 0;
if ($update_ref) {
- $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
- $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+ $sigmask = POSIX::SigSet->new();
+ my $signew = POSIX::SigSet->new(SIGINT, SIGHUP, SIGTERM,
+ SIGALRM, SIGUSR1, SIGUSR2);
+ sigprocmask(SIG_BLOCK, $signew, $sigmask) or
+ croak "Can't block signals: $!";
}
mkfile($db);
"$db_lock => $db ($!)\n";
delete $LOCKFILES{$db_lock};
if ($update_ref) {
- $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
- $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
- kill $sig, $$ if defined $sig;
+ sigprocmask(SIG_SETMASK, $sigmask) or
+ croak "Can't restore signal mask: $!";
}
}
}
sub _auth_providers () {
- [
+ my @rv = (
SVN::Client::get_simple_provider(),
SVN::Client::get_ssl_server_trust_file_provider(),
SVN::Client::get_simple_prompt_provider(
\&Git::SVN::Prompt::ssl_server_trust),
SVN::Client::get_username_prompt_provider(
\&Git::SVN::Prompt::username, 2)
- ]
+ );
+
+ # earlier 1.6.x versions would segfault, and <= 1.5.x didn't have
+ # this function
+ if (::compare_svn_version('1.6.12') > 0) {
+ my $config = SVN::Core::config_get_config($config_dir);
+ my ($p, @a);
+ # config_get_config returns all config files from
+ # ~/.subversion, auth_get_platform_specific_client_providers
+ # just wants the config "file".
+ @a = ($config->{'config'}, undef);
+ $p = SVN::Core::auth_get_platform_specific_client_providers(@a);
+ # Insert the return value from
+ # auth_get_platform_specific_providers
+ unshift @rv, @$p;
+ }
+ \@rv;
}
sub escape_uri_only {
# drop it. Therefore, the receiver callback passed to it
# is made aware of this limitation by being wrapped if
# the limit passed to is being wrapped.
- if ($SVN::Core::VERSION le '1.2.0') {
+ if (::compare_svn_version('1.2.0') <= 0) {
my $limit = splice(@args, 3, 1);
if ($limit > 0) {
my $receiver = pop @args;
sub get_commit_editor {
my ($self, $log, $cb, $pool) = @_;
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();
$self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
}
my (@pc) = split m#/#, $path;
my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
1, $editor, $pool);
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
# Since we can't rely on svn_ra_reparent being available, we'll
# just have to do some magic with set_path to make it so
$ra ||= $self;
$url_b = escape_url($url_b);
my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
$reporter->set_path('', $rev_a, 0, @lock, $pool);
$reporter->finish_report($pool);
" <command> [<args>]";
const char git_more_info_string[] =
- "See 'git help <command>' for more information on a specific command.";
+ N_("See 'git help <command>' for more information on a specific command.");
static struct startup_info git_startup_info;
static int use_pager = -1;
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
{ "clone", cmd_clone },
+ { "column", cmd_column, RUN_SETUP_GENTLY },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY },
# (only effective if this variable evaluates to true)
our $export_ok = "++GITWEB_EXPORT_OK++";
+# don't generate age column on the projects list page
+our $omit_age_column = 0;
+
+# don't generate information about owners of repositories
+our $omit_owner=0;
+
# show repository only if this subroutine returns true
# when given the path to the project, for example:
# sub { return -e "$_[0]/git-daemon-export-ok"; }
# '<span class="mark">foo</span>bar'
sub esc_html_hl_regions {
my ($str, $css_class, @sel) = @_;
- return esc_html($str) unless @sel;
+ my %opts = grep { ref($_) ne 'ARRAY' } @sel;
+ @sel = grep { ref($_) eq 'ARRAY' } @sel;
+ return esc_html($str, %opts) unless @sel;
my $out = '';
my $pos = 0;
for my $s (@sel) {
- $out .= esc_html(substr($str, $pos, $s->[0] - $pos))
- if ($s->[0] - $pos > 0);
- $out .= $cgi->span({-class => $css_class},
- esc_html(substr($str, $s->[0], $s->[1] - $s->[0])));
+ my ($begin, $end) = @$s;
+
+ # Don't create empty <span> elements.
+ next if $end <= $begin;
+
+ my $escaped = esc_html(substr($str, $begin, $end - $begin),
+ %opts);
- $pos = $s->[1];
+ $out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
+ if ($begin - $pos > 0);
+ $out .= $cgi->span({-class => $css_class}, $escaped);
+
+ $pos = $end;
}
- $out .= esc_html(substr($str, $pos))
+ $out .= esc_html(substr($str, $pos), %opts)
if ($pos < length($str));
return $out;
}
# process patch (diff) line (not to be used for diff headers),
-# returning class and HTML-formatted (but not wrapped) line
-sub process_diff_line {
- my $line = shift;
- my ($from, $to) = @_;
-
- my $diff_class = diff_line_class($line, $from, $to);
-
- chomp $line;
- $line = untabify($line);
+# returning HTML-formatted (but not wrapped) line.
+# If the line is passed as a reference, it is treated as HTML and not
+# esc_html()'ed.
+sub format_diff_line {
+ my ($line, $diff_class, $from, $to) = @_;
+
+ if (ref($line)) {
+ $line = $$line;
+ } else {
+ chomp $line;
+ $line = untabify($line);
- if ($from && $to && $line =~ m/^\@{2} /) {
- $line = format_unidiff_chunk_header($line, $from, $to);
- return $diff_class, $line;
+ if ($from && $to && $line =~ m/^\@{2} /) {
+ $line = format_unidiff_chunk_header($line, $from, $to);
+ } elsif ($from && $to && $line =~ m/^\@{3}/) {
+ $line = format_cc_diff_chunk_header($line, $from, $to);
+ } else {
+ $line = esc_html($line, -nbsp=>1);
+ }
+ }
- } elsif ($from && $to && $line =~ m/^\@{3}/) {
- $line = format_cc_diff_chunk_header($line, $from, $to);
- return $diff_class, $line;
+ my $diff_classes = "diff";
+ $diff_classes .= " $diff_class" if ($diff_class);
+ $line = "<div class=\"$diff_classes\">$line</div>\n";
- }
- return $diff_class, esc_html($line, -nbsp=>1);
+ return $line;
}
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
}
if (check_export_ok("$projectroot/$path")) {
my $pr = {
- path => $path,
- owner => to_utf8($owner),
+ path => $path
};
+ if ($owner) {
+ $pr->{'owner'} = to_utf8($owner);
+ }
push @list, $pr;
}
}
'-type' => "application/$type+xml"
);
+ $href_params{'extra_options'} = undef;
$href_params{'action'} = $type;
$link_attr{'-href'} = href(%href_params);
print "<link ".
print "</table>\n";
}
-sub print_sidebyside_diff_chunk {
- my @chunk = @_;
+# Print context lines and then rem/add lines in a side-by-side manner.
+sub print_sidebyside_diff_lines {
+ my ($ctx, $rem, $add) = @_;
+
+ # print context block before add/rem block
+ if (@$ctx) {
+ print join '',
+ '<div class="chunk_block ctx">',
+ '<div class="old">',
+ @$ctx,
+ '</div>',
+ '<div class="new">',
+ @$ctx,
+ '</div>',
+ '</div>';
+ }
+
+ if (!@$add) {
+ # pure removal
+ print join '',
+ '<div class="chunk_block rem">',
+ '<div class="old">',
+ @$rem,
+ '</div>',
+ '</div>';
+ } elsif (!@$rem) {
+ # pure addition
+ print join '',
+ '<div class="chunk_block add">',
+ '<div class="new">',
+ @$add,
+ '</div>',
+ '</div>';
+ } else {
+ print join '',
+ '<div class="chunk_block chg">',
+ '<div class="old">',
+ @$rem,
+ '</div>',
+ '<div class="new">',
+ @$add,
+ '</div>',
+ '</div>';
+ }
+}
+
+# Print context lines and then rem/add lines in inline manner.
+sub print_inline_diff_lines {
+ my ($ctx, $rem, $add) = @_;
+
+ print @$ctx, @$rem, @$add;
+}
+
+# Format removed and added line, mark changed part and HTML-format them.
+# Implementation is based on contrib/diff-highlight
+sub format_rem_add_lines_pair {
+ my ($rem, $add, $num_parents) = @_;
+
+ # We need to untabify lines before split()'ing them;
+ # otherwise offsets would be invalid.
+ chomp $rem;
+ chomp $add;
+ $rem = untabify($rem);
+ $add = untabify($add);
+
+ my @rem = split(//, $rem);
+ my @add = split(//, $add);
+ my ($esc_rem, $esc_add);
+ # Ignore leading +/- characters for each parent.
+ my ($prefix_len, $suffix_len) = ($num_parents, 0);
+ my ($prefix_has_nonspace, $suffix_has_nonspace);
+
+ my $shorter = (@rem < @add) ? @rem : @add;
+ while ($prefix_len < $shorter) {
+ last if ($rem[$prefix_len] ne $add[$prefix_len]);
+
+ $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
+ $prefix_len++;
+ }
+
+ while ($prefix_len + $suffix_len < $shorter) {
+ last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
+
+ $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
+ $suffix_len++;
+ }
+
+ # Mark lines that are different from each other, but have some common
+ # part that isn't whitespace. If lines are completely different, don't
+ # mark them because that would make output unreadable, especially if
+ # diff consists of multiple lines.
+ if ($prefix_has_nonspace || $suffix_has_nonspace) {
+ $esc_rem = esc_html_hl_regions($rem, 'marked',
+ [$prefix_len, @rem - $suffix_len], -nbsp=>1);
+ $esc_add = esc_html_hl_regions($add, 'marked',
+ [$prefix_len, @add - $suffix_len], -nbsp=>1);
+ } else {
+ $esc_rem = esc_html($rem, -nbsp=>1);
+ $esc_add = esc_html($add, -nbsp=>1);
+ }
+
+ return format_diff_line(\$esc_rem, 'rem'),
+ format_diff_line(\$esc_add, 'add');
+}
+
+# HTML-format diff context, removed and added lines.
+sub format_ctx_rem_add_lines {
+ my ($ctx, $rem, $add, $num_parents) = @_;
+ my (@new_ctx, @new_rem, @new_add);
+ my $can_highlight = 0;
+ my $is_combined = ($num_parents > 1);
+
+ # Highlight if every removed line has a corresponding added line.
+ if (@$add > 0 && @$add == @$rem) {
+ $can_highlight = 1;
+
+ # Highlight lines in combined diff only if the chunk contains
+ # diff between the same version, e.g.
+ #
+ # - a
+ # - b
+ # + c
+ # + d
+ #
+ # Otherwise the highlightling would be confusing.
+ if ($is_combined) {
+ for (my $i = 0; $i < @$add; $i++) {
+ my $prefix_rem = substr($rem->[$i], 0, $num_parents);
+ my $prefix_add = substr($add->[$i], 0, $num_parents);
+
+ $prefix_rem =~ s/-/+/g;
+
+ if ($prefix_rem ne $prefix_add) {
+ $can_highlight = 0;
+ last;
+ }
+ }
+ }
+ }
+
+ if ($can_highlight) {
+ for (my $i = 0; $i < @$add; $i++) {
+ my ($line_rem, $line_add) = format_rem_add_lines_pair(
+ $rem->[$i], $add->[$i], $num_parents);
+ push @new_rem, $line_rem;
+ push @new_add, $line_add;
+ }
+ } else {
+ @new_rem = map { format_diff_line($_, 'rem') } @$rem;
+ @new_add = map { format_diff_line($_, 'add') } @$add;
+ }
+
+ @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
+
+ return (\@new_ctx, \@new_rem, \@new_add);
+}
+
+# Print context lines and then rem/add lines.
+sub print_diff_lines {
+ my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
+ my $is_combined = $num_parents > 1;
+
+ ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
+ $num_parents);
+
+ if ($diff_style eq 'sidebyside' && !$is_combined) {
+ print_sidebyside_diff_lines($ctx, $rem, $add);
+ } else {
+ # default 'inline' style and unknown styles
+ print_inline_diff_lines($ctx, $rem, $add);
+ }
+}
+
+sub print_diff_chunk {
+ my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
my (@ctx, @rem, @add);
+ # The class of the previous line.
+ my $prev_class = '';
+
return unless @chunk;
# incomplete last line might be among removed or added lines,
# print chunk headers
if ($class && $class eq 'chunk_header') {
- print $line;
+ print format_diff_line($line, $class, $from, $to);
next;
}
- ## print from accumulator when type of class of lines change
- # empty contents block on start rem/add block, or end of chunk
- if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
- print join '',
- '<div class="chunk_block ctx">',
- '<div class="old">',
- @ctx,
- '</div>',
- '<div class="new">',
- @ctx,
- '</div>',
- '</div>';
- @ctx = ();
- }
- # empty add/rem block on start context block, or end of chunk
- if ((@rem || @add) && (!$class || $class eq 'ctx')) {
- if (!@add) {
- # pure removal
- print join '',
- '<div class="chunk_block rem">',
- '<div class="old">',
- @rem,
- '</div>',
- '</div>';
- } elsif (!@rem) {
- # pure addition
- print join '',
- '<div class="chunk_block add">',
- '<div class="new">',
- @add,
- '</div>',
- '</div>';
- } else {
- # assume that it is change
- print join '',
- '<div class="chunk_block chg">',
- '<div class="old">',
- @rem,
- '</div>',
- '<div class="new">',
- @add,
- '</div>',
- '</div>';
- }
- @rem = @add = ();
+ ## print from accumulator when have some add/rem lines or end
+ # of chunk (flush context lines), or when have add and rem
+ # lines and new block is reached (otherwise add/rem lines could
+ # be reordered)
+ if (!$class || ((@rem || @add) && $class eq 'ctx') ||
+ (@rem && @add && $class ne $prev_class)) {
+ print_diff_lines(\@ctx, \@rem, \@add,
+ $diff_style, $num_parents);
+ @ctx = @rem = @add = ();
}
## adding lines to accumulator
if ($class eq 'ctx') {
push @ctx, $line;
}
+
+ $prev_class = $class;
}
}
next PATCH if ($patch_line =~ m/^diff /);
- my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
- my $diff_classes = "diff";
- $diff_classes .= " $class" if ($class);
- $line = "<div class=\"$diff_classes\">$line</div>\n";
+ my $class = diff_line_class($patch_line, \%from, \%to);
- if ($diff_style eq 'sidebyside' && !$is_combined) {
- if ($class eq 'chunk_header') {
- print_sidebyside_diff_chunk(@chunk);
- @chunk = ( [ $class, $line ] );
- } else {
- push @chunk, [ $class, $line ];
- }
- } else {
- # default 'inline' style and unknown styles
- print $line;
+ if ($class eq 'chunk_header') {
+ print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
+ @chunk = ();
}
+
+ push @chunk, [ $class, $patch_line ];
}
} continue {
if (@chunk) {
- print_sidebyside_diff_chunk(@chunk);
+ print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
@chunk = ();
}
print "</div>\n"; # class="patch"
? esc_html_match_hl_chopped($pr->{'descr_long'},
$pr->{'descr'}, $search_regexp)
: esc_html($pr->{'descr'})) .
- "</td>\n" .
- "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
- print "<td class=\"". age_class($pr->{'age'}) . "\">" .
- (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
- "<td class=\"link\">" .
+ "</td>\n";
+ unless ($omit_owner) {
+ print "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
+ }
+ unless ($omit_age_column) {
+ print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+ (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n";
+ }
+ print"<td class=\"link\">" .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
'tagfilter' => $tagfilter)
if ($tagfilter || $search_regexp);
# fill the rest
- @projects = fill_project_list_info(\@projects);
+ my @all_fields = ('descr', 'descr_long', 'ctags', 'category');
+ push @all_fields, ('age', 'age_string') unless($omit_age_column);
+ push @all_fields, 'owner' unless($omit_owner);
+ @projects = fill_project_list_info(\@projects, @all_fields);
$order ||= $default_projects_order;
$from = 0 unless defined $from;
}
print_sort_th('project', $order, 'Project');
print_sort_th('descr', $order, 'Description');
- print_sort_th('owner', $order, 'Owner');
- print_sort_th('age', $order, 'Last Change');
+ print_sort_th('owner', $order, 'Owner') unless $omit_owner;
+ print_sort_th('age', $order, 'Last Change') unless $omit_age_column;
print "<th></th>\n" . # for links
"</tr>\n";
}
print "<div class=\"title\"> </div>\n";
print "<table class=\"projects_list\">\n" .
- "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
- "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+ "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
+ unless ($omit_owner) {
+ print "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+ }
if (defined $cd{'rfc2822'}) {
print "<tr id=\"metadata_lchange\"><td>last change</td>" .
"<td>".format_timestamp_html(\%cd)."</td></tr>\n";
return wantarray ? ($name, $name) : $name;
}
+sub exit_if_unmodified_since {
+ my ($latest_epoch) = @_;
+ our $cgi;
+
+ my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+ if (defined $if_modified) {
+ my $since;
+ if (eval { require HTTP::Date; 1; }) {
+ $since = HTTP::Date::str2time($if_modified);
+ } elsif (eval { require Time::ParseDate; 1; }) {
+ $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+ }
+ if (defined $since && $latest_epoch <= $since) {
+ my %latest_date = parse_date($latest_epoch);
+ print $cgi->header(
+ -last_modified => $latest_date{'rfc2822'},
+ -status => '304 Not Modified');
+ goto DONE_GITWEB;
+ }
+ }
+}
+
sub git_snapshot {
my $format = $input_params{'snapshot_format'};
if (!@snapshot_fmts) {
my ($name, $prefix) = snapshot_name($project, $hash);
my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+
+ my %co = parse_commit($hash);
+ exit_if_unmodified_since($co{'committer_epoch'}) if %co;
+
my $cmd = quote_command(
git_cmd(), 'archive',
"--format=$known_snapshot_formats{$format}{'format'}",
}
$filename =~ s/(["\\])/\\$1/g;
+ my %latest_date;
+ if (%co) {
+ %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+ }
+
print $cgi->header(
-type => $known_snapshot_formats{$format}{'type'},
-content_disposition => 'inline; filename="' . $filename . '"',
+ %co ? (-last_modified => $latest_date{'rfc2822'}) : (),
-status => '200 OK');
open my $fd, "-|", $cmd
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
my $latest_epoch = $latest_commit{'committer_epoch'};
- %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
- my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
- if (defined $if_modified) {
- my $since;
- if (eval { require HTTP::Date; 1; }) {
- $since = HTTP::Date::str2time($if_modified);
- } elsif (eval { require Time::ParseDate; 1; }) {
- $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
- }
- if (defined $since && $latest_epoch <= $since) {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'},
- -status => '304 Not Modified');
- return;
- }
- }
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'});
- } else {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8');
+ exit_if_unmodified_since($latest_epoch);
+ %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
}
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8',
+ %latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
+ -status => '200 OK');
# Optimization: skip generating the body if client asks only
# for Last-Modified date.
color: #008800;
}
+div.diff.add span.marked {
+ background-color: #aaffaa;
+}
+
div.diff.from_file a.path,
div.diff.from_file {
color: #aa0000;
color: #cc0000;
}
+div.diff.rem span.marked {
+ background-color: #ffaaaa;
+}
+
div.diff.chunk_header a,
div.diff.chunk_header {
color: #990099;
#include "levenshtein.h"
#include "help.h"
#include "common-cmds.h"
+#include "string-list.h"
+#include "column.h"
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
cmds->cnt = cj;
}
-static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+static void pretty_print_string_list(struct cmdnames *cmds,
+ unsigned int colopts)
{
- int cols = 1, rows;
- int space = longest + 1; /* min 1 SP between words */
- int max_cols = term_columns() - 1; /* don't print *on* the edge */
- int i, j;
-
- if (space < max_cols)
- cols = max_cols / space;
- rows = DIV_ROUND_UP(cmds->cnt, cols);
-
- for (i = 0; i < rows; i++) {
- printf(" ");
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ struct column_options copts;
+ int i;
- for (j = 0; j < cols; j++) {
- int n = j * rows + i;
- int size = space;
- if (n >= cmds->cnt)
- break;
- if (j == cols-1 || n + rows >= cmds->cnt)
- size = 1;
- printf("%-*s", size, cmds->names[n]->name);
- }
- putchar('\n');
- }
+ for (i = 0; i < cmds->cnt; i++)
+ string_list_append(&list, cmds->names[i]->name);
+ /*
+ * always enable column display, we only consult column.*
+ * about layout strategy and stuff
+ */
+ colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(&list, colopts, &copts);
+ string_list_clear(&list, 0);
}
static int is_executable(const char *name)
exclude_cmds(other_cmds, main_cmds);
}
-void list_commands(const char *title, struct cmdnames *main_cmds,
- struct cmdnames *other_cmds)
+void list_commands(unsigned int colopts,
+ struct cmdnames *main_cmds, struct cmdnames *other_cmds)
{
- int i, longest = 0;
-
- for (i = 0; i < main_cmds->cnt; i++)
- if (longest < main_cmds->names[i]->len)
- longest = main_cmds->names[i]->len;
- for (i = 0; i < other_cmds->cnt; i++)
- if (longest < other_cmds->names[i]->len)
- longest = other_cmds->names[i]->len;
-
if (main_cmds->cnt) {
const char *exec_path = git_exec_path();
- printf("available %s in '%s'\n", title, exec_path);
- printf("----------------");
- mput_char('-', strlen(title) + strlen(exec_path));
+ printf_ln(_("available git commands in '%s'"), exec_path);
putchar('\n');
- pretty_print_string_list(main_cmds, longest);
+ pretty_print_string_list(main_cmds, colopts);
putchar('\n');
}
if (other_cmds->cnt) {
- printf("%s available from elsewhere on your $PATH\n", title);
- printf("---------------------------------------");
- mput_char('-', strlen(title));
+ printf_ln(_("git commands available from elsewhere on your $PATH"));
putchar('\n');
- pretty_print_string_list(other_cmds, longest);
+ pretty_print_string_list(other_cmds, colopts);
putchar('\n');
}
}
sizeof(*main_cmds.names), levenshtein_compare);
if (!main_cmds.cnt)
- die ("Uh oh. Your system reports no Git commands at all.");
+ die(_("Uh oh. Your system reports no Git commands at all."));
/* skip and count prefix matches */
for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
clean_cmdnames(&main_cmds);
- fprintf(stderr, "WARNING: You called a Git command named '%s', "
- "which does not exist.\n"
- "Continuing under the assumption that you meant '%s'\n",
+ fprintf_ln(stderr,
+ _("WARNING: You called a Git command named '%s', "
+ "which does not exist.\n"
+ "Continuing under the assumption that you meant '%s'"),
cmd, assumed);
if (autocorrect > 0) {
- fprintf(stderr, "in %0.1f seconds automatically...\n",
+ fprintf_ln(stderr, _("in %0.1f seconds automatically..."),
(float)autocorrect/10.0);
poll(NULL, 0, autocorrect * 100);
}
return assumed;
}
- fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd);
+ fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
if (SIMILAR_ENOUGH(best_similarity)) {
- fprintf(stderr, "\nDid you mean %s?\n",
- n < 2 ? "this": "one of these");
+ fprintf_ln(stderr,
+ Q_("\nDid you mean this?",
+ "\nDid you mean one of these?",
+ n));
for (i = 0; i < n; i++)
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
/* Here we require that excludes is a sorted list. */
extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-extern void list_commands(const char *title,
- struct cmdnames *main_cmds,
- struct cmdnames *other_cmds);
+extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
#endif /* HELP_H */
if (repo->path)
url = repo->path;
if (strncmp(path, url, repo->path_len))
- error("Parsed path '%s' does not match url: '%s'\n",
+ error("Parsed path '%s' does not match url: '%s'",
path, url);
else {
path += repo->path_len;
run_active_slot(slot);
free(url);
if (results.curl_result != CURLE_OK)
- return error("DELETE request failed (%d/%ld)\n",
+ return error("DELETE request failed (%d/%ld)",
results.curl_result, results.http_code);
} else {
free(url);
tmp = strbuf_detach(&buf, NULL);
if (http_get_file(url, tmp, 0) != HTTP_OK) {
- error("Unable to get pack index %s\n", url);
+ error("Unable to get pack index %s", url);
free(tmp);
tmp = NULL;
}
ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
if (ret != strlen(response))
- return error("IMAP error: sending response failed\n");
+ return error("IMAP error: sending response failed");
free(response);
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 (%s vs. %s)\n",
+ warning("Cannot merge binary files: %s (%s vs. %s)",
path, name1, name2);
return ll_binary_merge(drv_unused, result,
path,
--- /dev/null
+#include "cache.h"
+#include "mergesort.h"
+
+struct mergesort_sublist {
+ void *ptr;
+ unsigned long len;
+};
+
+static void *get_nth_next(void *list, unsigned long n,
+ void *(*get_next_fn)(const void *))
+{
+ while (n-- && list)
+ list = get_next_fn(list);
+ return list;
+}
+
+static void *pop_item(struct mergesort_sublist *l,
+ void *(*get_next_fn)(const void *))
+{
+ void *p = l->ptr;
+ l->ptr = get_next_fn(l->ptr);
+ l->len = l->ptr ? (l->len - 1) : 0;
+ return p;
+}
+
+void *llist_mergesort(void *list,
+ void *(*get_next_fn)(const void *),
+ void (*set_next_fn)(void *, void *),
+ int (*compare_fn)(const void *, const void *))
+{
+ unsigned long l;
+
+ if (!list)
+ return NULL;
+ for (l = 1; ; l *= 2) {
+ void *curr;
+ struct mergesort_sublist p, q;
+
+ p.ptr = list;
+ q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+ if (!q.ptr)
+ break;
+ p.len = q.len = l;
+
+ if (compare_fn(p.ptr, q.ptr) > 0)
+ list = curr = pop_item(&q, get_next_fn);
+ else
+ list = curr = pop_item(&p, get_next_fn);
+
+ while (p.ptr) {
+ while (p.len || q.len) {
+ void *prev = curr;
+
+ if (!p.len)
+ curr = pop_item(&q, get_next_fn);
+ else if (!q.len)
+ curr = pop_item(&p, get_next_fn);
+ else if (compare_fn(p.ptr, q.ptr) > 0)
+ curr = pop_item(&q, get_next_fn);
+ else
+ curr = pop_item(&p, get_next_fn);
+ set_next_fn(prev, curr);
+ }
+ p.ptr = q.ptr;
+ p.len = l;
+ q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+ q.len = q.ptr ? l : 0;
+
+ }
+ set_next_fn(curr, NULL);
+ }
+ return list;
+}
--- /dev/null
+#ifndef MERGESORT_H
+#define MERGESORT_H
+
+/*
+ * Sort linked list in place.
+ * - get_next_fn() returns the next element given an element of a linked list.
+ * - set_next_fn() takes two elements A and B, and makes B the "next" element
+ * of A on the list.
+ * - compare_fn() takes two elements A and B, and returns negative, 0, positive
+ * as the same sign as "subtracting" B from A.
+ */
+void *llist_mergesort(void *list,
+ void *(*get_next_fn)(const void *),
+ void (*set_next_fn)(void *, void *),
+ int (*compare_fn)(const void *, const void *));
+
+#endif
obj = &tag->object;
}
} else {
- warning("object %s has unknown type id %d\n", sha1_to_hex(sha1), type);
+ warning("object %s has unknown type id %d", sha1_to_hex(sha1), type);
obj = NULL;
}
if (obj && obj->type == OBJ_NONE)
if (obj && obj->parsed)
return obj;
+ if ((obj && obj->type == OBJ_BLOB) ||
+ (!obj && has_sha1_file(sha1) &&
+ sha1_object_info(sha1, NULL) == OBJ_BLOB)) {
+ if (check_sha1_signature(repl, NULL, 0, NULL) < 0) {
+ error("sha1 mismatch %s", sha1_to_hex(repl));
+ return NULL;
+ }
+ parse_blob_buffer(lookup_blob(sha1), NULL, 0);
+ return lookup_object(sha1);
+ }
+
buffer = read_sha1_file(sha1, &type, &size);
if (buffer) {
if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
free(buffer);
- error("sha1 mismatch %s\n", sha1_to_hex(repl));
+ error("sha1 mismatch %s", sha1_to_hex(repl));
return NULL;
}
array->nr = dst;
}
}
+
+void clear_object_flags(unsigned flags)
+{
+ int i;
+
+ for (i=0; i < obj_hash_size; i++) {
+ struct object *obj = obj_hash[i];
+ if (obj)
+ obj->flags &= ~flags;
+ }
+}
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
void object_array_remove_duplicates(struct object_array *);
+void clear_object_flags(unsigned flags);
+
#endif /* OBJECT_H */
{
const char *pager = git_pager(isatty(1));
- if (!pager)
+ if (!pager || pager_in_use())
return;
/*
s = literal ? "[%s]" : "[<%s>]";
else
s = literal ? " %s" : " <%s>";
- return fprintf(outfile, s, opts->argh ? opts->argh : "...");
+ return fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
}
#define USAGE_OPTS_WIDTH 24
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fprintf(outfile, "cat <<\\EOF\n");
- fprintf(outfile, "usage: %s\n", *usagestr++);
+ fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
while (*usagestr && **usagestr)
- fprintf(outfile, " or: %s\n", *usagestr++);
+ /* TRANSLATORS: the colon here should align with the
+ one in "usage: %s" translation */
+ fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
while (*usagestr) {
- fprintf(outfile, "%s%s\n",
- **usagestr ? " " : "",
- *usagestr);
+ if (**usagestr)
+ fprintf_ln(outfile, _(" %s"), _(*usagestr));
+ else
+ putchar('\n');
usagestr++;
}
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
if (*opts->help)
- fprintf(outfile, "%s\n", opts->help);
+ fprintf(outfile, "%s\n", _(opts->help));
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
fputc('\n', outfile);
pad = USAGE_OPTS_WIDTH;
}
- fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+ fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help));
}
fputc('\n', outfile);
*
* `argh`::
* token to explain the kind of argument this option wants. Keep it
- * homogeneous across the repository.
+ * homogeneous across the repository. Should be wrapped by N_() for
+ * translation.
*
* `help`::
* the short help associated to what the option does.
* Must never be NULL (except for OPTION_END).
* OPTION_GROUP uses this pointer to store the group header.
+ * Should be wrapped by N_() for translation.
*
* `flags`::
* mask of parse_opt_option_flags.
#define OPT_BOOL(s, l, v, h) OPT_SET_INT(s, l, v, h, 1)
#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, NULL, (p) }
-#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) }
+#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h) }
#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) }
#define OPT_STRING_LIST(s, l, v, a, h) \
{ OPTION_CALLBACK, (s), (l), (v), (a), \
#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, &parse_opt_tertiary }
#define OPT_DATE(s, l, v, h) \
- { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \
+ { OPTION_CALLBACK, (s), (l), (v), N_("time"),(h), 0, \
parse_opt_approxidate_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \
- "file", (h) }
+ N_("file"), (h) }
#define OPT_COLOR_FLAG(s, l, v, h) \
- { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
+ { OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \
parse_opt_color_flag_cb, (intptr_t)"always" }
#define OPT_NOOP_NOARG(s, l) \
{ OPTION_CALLBACK, (s), (l), NULL, NULL, \
- "no-op (backward compatibility)", \
+ N_("no-op (backward compatibility)"), \
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
/* Deprecated synonym */
#define OPT_BOOLEAN OPT_COUNTUP
/* parse_options() will filter out the processed options and leave the
- * non-option arguments in argv[].
+ * non-option arguments in argv[]. usagestr strings should be marked
+ * for translation with N_().
* Returns the number of arguments left in argv[].
*/
extern int parse_options(int argc, const char **argv, const char *prefix,
#define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h))
#define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h))
#define OPT__VERBOSITY(var) \
- { OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
+ { OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
- { OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
+ { OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
#define OPT__DRY_RUN(var, h) OPT_BOOLEAN('n', "dry-run", (var), (h))
#define OPT__FORCE(var, h) OPT_BOOLEAN('f', "force", (var), (h))
#define OPT__ABBREV(var) \
- { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
- "use <n> digits to display SHA-1s", \
+ { OPTION_CALLBACK, 0, "abbrev", (var), N_("n"), \
+ N_("use <n> digits to display SHA-1s"), \
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
#define OPT__COLOR(var, h) \
OPT_COLOR_FLAG(0, "color", (var), (h))
+#define OPT_COLUMN(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
#endif
# German translations for Git.
-# Copyright (C) 2012 Ralf Thielow <ralf.thielow@googlemail.com>
+# Copyright (C) 2010-2012 Ralf Thielow <ralf.thielow@googlemail.com>
# This file is distributed under the same license as the Git package.
-# Ralf Thielow <ralf.thielow@googlemail.com>, 2012.
+# Ralf Thielow <ralf.thielow@googlemail.com>, 2010, 2011, 2012.
#
msgid ""
msgstr ""
"um die Auflösung entsprechend zu markieren und einzutragen,\n"
"oder benutze 'git commit -a'."
-#: commit.c:47
+#: commit.c:48
#, c-format
msgid "could not parse %s"
msgstr "konnte %s nicht parsen"
-#: commit.c:49
+#: commit.c:50
#, c-format
msgid "%s %s is not a commit!"
msgstr "%s %s ist keine Version!"
#: sequencer.c:928
msgid "Can't cherry-pick into empty head"
-msgstr "Kann \"cherry-pick\" nicht in einen leeren Kopf ausführen."
+msgstr "Kann \"cherry-pick\" nicht in einem leerem Kopf ausführen."
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "Zweigspitze (HEAD) zeigt auf keinen Zweig"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Kein solcher Zweig '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Kein entferntes Projektarchiv für Zweig '%s' konfiguriert."
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "Zweig '%s' des entfernten Projektarchivs ist nicht als entfernter "
+"Übernahmezweig gespeichert"
#: wt-status.c:134
msgid "Unmerged paths:"
#: builtin/add.c:209
#, c-format
msgid "'%s' is beyond a symbolic link"
-msgstr "'%s' ist über einer symbolischen Verknüpfung"
+msgstr "'%s' ist über einem symbolischen Link"
#: builtin/add.c:276
msgid "Could not read the index"
#: builtin/archive.c:71
msgid "git archive: expected a flush"
-msgstr "git archive: erwartete eine Leerung (flush)"
+msgstr "git archive: erwartete eine Spülung (flush)"
#: builtin/branch.c:137
#, c-format
" '%s', obwohl er mit der Zweigspitze (HEAD) zusammengeführt wurde."
#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
+#: builtin/branch.c:164
msgid "remote "
msgstr "externer "
-#: builtin/branch.c:171
+#: builtin/branch.c:172
msgid "cannot use -a with -d"
msgstr "kann -a nicht mit -d benutzen"
-#: builtin/branch.c:177
+#: builtin/branch.c:178
msgid "Couldn't look up commit object for HEAD"
msgstr "Konnte Versionsobjekt für Zweigspitze (HEAD) nicht nachschlagen."
-#: builtin/branch.c:182
+#: builtin/branch.c:183
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr ""
"Kann Zweig '%s' nicht entfernen, da du dich gerade auf diesem befindest."
-#: builtin/branch.c:192
+#: builtin/branch.c:193
#, c-format
msgid "%sbranch '%s' not found."
msgstr "%sZweig '%s' nicht gefunden."
-#: builtin/branch.c:200
+#: builtin/branch.c:201
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen."
-#: builtin/branch.c:206
+#: builtin/branch.c:207
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"Der Zweig '%s' ist nicht vollständig zusammengeführt.\n"
"Wenn du sicher bist diesen Zweig zu entfernen, führe 'git branch -D %s' aus."
-#: builtin/branch.c:214
+#: builtin/branch.c:215
#, c-format
msgid "Error deleting %sbranch '%s'"
msgstr "Fehler beim Löschen von %sZweig '%s'"
-#: builtin/branch.c:219
+#: builtin/branch.c:221
#, c-format
msgid "Deleted %sbranch %s (was %s).\n"
msgstr "Entferne %sZweig %s (war %s).\n"
-#: builtin/branch.c:224
+#: builtin/branch.c:226
msgid "Update of config-file failed"
msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen."
-#: builtin/branch.c:322
+#: builtin/branch.c:324
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "Zweig '%s' zeigt auf keine Version"
-#: builtin/branch.c:394
+#: builtin/branch.c:396
#, c-format
msgid "behind %d] "
msgstr "%d hinterher] "
-#: builtin/branch.c:396
+#: builtin/branch.c:398
#, c-format
msgid "ahead %d] "
msgstr "%d voraus] "
-#: builtin/branch.c:398
+#: builtin/branch.c:400
#, c-format
msgid "ahead %d, behind %d] "
msgstr "%d voraus, %d hinterher] "
-#: builtin/branch.c:501
+#: builtin/branch.c:503
msgid "(no branch)"
msgstr "(kein Zweig)"
-#: builtin/branch.c:566
+#: builtin/branch.c:568
msgid "some refs could not be read"
msgstr "Konnte einige Referenzen nicht lesen"
-#: builtin/branch.c:579
+#: builtin/branch.c:581
msgid "cannot rename the current branch while not on any."
msgstr ""
"Kann aktuellen Zweig nicht umbennen, solange du dich auf keinem befindest."
-#: builtin/branch.c:589
+#: builtin/branch.c:591
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "Ungültiger Zweig-Name: '%s'"
-#: builtin/branch.c:604
+#: builtin/branch.c:606
msgid "Branch rename failed"
msgstr "Umbenennung des Zweiges fehlgeschlagen"
-#: builtin/branch.c:608
+#: builtin/branch.c:610
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "falsch benannten Zweig '%s' umbenannt"
-#: builtin/branch.c:612
+#: builtin/branch.c:614
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "Zweig umbenannt zu %s, aber Zweigspitze (HEAD) ist nicht aktualisiert!"
-#: builtin/branch.c:619
+#: builtin/branch.c:621
msgid "Branch is renamed, but update of config-file failed"
msgstr ""
"Zweig ist umbenannt, aber die Aktualisierung der Konfigurationsdatei ist "
"fehlgeschlagen."
-#: builtin/branch.c:634
+#: builtin/branch.c:636
#, c-format
msgid "malformed object name %s"
msgstr "Missgebildeter Objektname %s"
-#: builtin/branch.c:658
+#: builtin/branch.c:660
#, c-format
msgid "could not write branch description template: %s\n"
msgstr "Konnte Beschreibungsvorlage für Zweig nicht schreiben: %s\n"
-#: builtin/branch.c:746
+#: builtin/branch.c:750
msgid "Failed to resolve HEAD as a valid ref."
msgstr "Konnte Zweigspitze (HEAD) nicht als gültige Referenz auflösen."
-#: builtin/branch.c:751 builtin/clone.c:558
+#: builtin/branch.c:755 builtin/clone.c:558
msgid "HEAD not found below refs/heads!"
msgstr "Zweigspitze (HEAD) wurde nicht unter \"refs/heads\" gefunden!"
-#: builtin/branch.c:809
+#: builtin/branch.c:813
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
"Die Optionen -a und -r bei 'git branch' machen mit einem Zweignamen keinen "
msgid "Can not do reflog for '%s'\n"
msgstr "Konnte \"reflog\" für '%s' nicht durchführen\n"
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
msgid "HEAD is now at"
msgstr "Zweigspitze (HEAD) ist jetzt bei"
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
#, c-format
msgid "Reset branch '%s'\n"
msgstr "Setze Zweig '%s' zurück\n"
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
#, c-format
msgid "Already on '%s'\n"
msgstr "Bereits auf '%s'\n"
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "Gewechselt zu zurückgesetztem Zweig '%s'\n"
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "Gewechselt zu einem neuen Zweig '%s'\n"
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "Gewechselt zu Zweig '%s'\n"
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
#, c-format
msgid " ... and %d more.\n"
msgstr " ... und %d weitere.\n"
#. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
#: builtin/clean.c:166
#, c-format
msgid "Would not remove %s\n"
-msgstr "Würde %s nicht entfernen\n"
+msgstr "Würde nicht entfernen %s\n"
#: builtin/clean.c:168
#, c-format
msgid "Not removing %s\n"
-msgstr "Entferne %s nicht\n"
+msgstr "Entferne nicht %s\n"
#: builtin/clone.c:243
#, c-format
#: builtin/commit.c:651
#, c-format
msgid "Malformed ident string: '%s'"
-msgstr "Fehlerhafter Identifikations-String: '%s'"
+msgstr "Fehlerhafte Identifikations-String: '%s'"
#: builtin/commit.c:689 builtin/commit.c:722 builtin/commit.c:1033
#, c-format
#: builtin/commit.c:1317
msgid "could not parse newly created commit"
-msgstr "Konnte neu erstellte Version nicht analysieren."
+msgstr "Konnte neulich erstellte Version nicht analysieren."
#: builtin/commit.c:1358
msgid "detached HEAD"
msgstr ""
"Das Projektarchiv wurde aktualisiert, aber die \"new_index\"-Datei\n"
"konnte nicht geschrieben werden. Prüfe, dass dein Speicher nicht\n"
-"voll und dein Kontingent nicht aufgebraucht ist und führe\n"
+"voll und Dein Kontingent nicht aufgebraucht ist und führe\n"
"anschließend \"git reset HEAD\" zu Wiederherstellung aus."
#: builtin/describe.c:234
#: builtin/diff.c:77
#, c-format
msgid "'%s': not a regular file or symlink"
-msgstr "'%s': keine reguläre Datei oder symbolische Verknüpfung"
+msgstr "'%s': keine reguläre Datei oder symbolischer Link"
#: builtin/diff.c:220
#, c-format
msgid "Cannot access work tree '%s'"
msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen."
-#: builtin/log.c:187
+#: builtin/log.c:188
#, c-format
msgid "Final output: %d %s\n"
msgstr "letzte Ausgabe: %d %s\n"
-#: builtin/log.c:395 builtin/log.c:483
+#: builtin/log.c:401 builtin/log.c:489
#, c-format
msgid "Could not read object %s"
msgstr "Kann Objekt %s nicht lesen."
-#: builtin/log.c:507
+#: builtin/log.c:513
#, c-format
msgid "Unknown type: %d"
msgstr "Unbekannter Typ: %d"
-#: builtin/log.c:596
+#: builtin/log.c:602
msgid "format.headers without value"
msgstr "format.headers ohne Wert"
-#: builtin/log.c:669
+#: builtin/log.c:675
msgid "name of output directory is too long"
msgstr "Name des Ausgabeverzeichnisses ist zu lang."
-#: builtin/log.c:680
+#: builtin/log.c:686
#, c-format
msgid "Cannot open patch file %s"
msgstr "Kann Patch-Datei %s nicht öffnen"
-#: builtin/log.c:694
+#: builtin/log.c:700
msgid "Need exactly one range."
msgstr "Brauche genau einen Versionsbereich."
-#: builtin/log.c:702
+#: builtin/log.c:708
msgid "Not a range."
msgstr "Kein Versionsbereich."
-#: builtin/log.c:739
+#: builtin/log.c:745
msgid "Could not extract email from committer identity."
msgstr "Konnte E-Mail-Adresse des Einreichers nicht extrahieren."
-#: builtin/log.c:785
+#: builtin/log.c:791
msgid "Cover letter needs email format"
-msgstr "Deckblatt benötigt E-Mail-Format"
+msgstr "Anschreiben benötigt E-Mail-Format"
-#: builtin/log.c:879
+#: builtin/log.c:885
#, c-format
msgid "insane in-reply-to: %s"
msgstr "ungültiges in-reply-to: %s"
-#: builtin/log.c:952
+#: builtin/log.c:958
msgid "Two output directories?"
msgstr "Zwei Ausgabeverzeichnisse?"
-#: builtin/log.c:1173
+#: builtin/log.c:1179
#, c-format
msgid "bogus committer info %s"
-msgstr "unechte Einreicher-Information %s"
+msgstr "unechte Einreicher-Informationen %s"
-#: builtin/log.c:1218
+#: builtin/log.c:1224
msgid "-n and -k are mutually exclusive."
msgstr "-n und -k schliessen sich gegenseitig aus"
-#: builtin/log.c:1220
+#: builtin/log.c:1226
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "--subject-prefix und -k schliessen sich gegenseitig aus"
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/log.c:1231 builtin/shortlog.c:284
#, c-format
msgid "unrecognized argument: %s"
msgstr "nicht erkanntes Argument: %s"
-#: builtin/log.c:1228
+#: builtin/log.c:1234
msgid "--name-only does not make sense"
msgstr "--name-only macht keinen Sinn"
-#: builtin/log.c:1230
+#: builtin/log.c:1236
msgid "--name-status does not make sense"
msgstr "--name-status macht keinen Sinn"
-#: builtin/log.c:1232
+#: builtin/log.c:1238
msgid "--check does not make sense"
msgstr "--check macht keinen Sinn"
-#: builtin/log.c:1255
+#: builtin/log.c:1261
msgid "standard output, or directory, which one?"
msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
-#: builtin/log.c:1257
+#: builtin/log.c:1263
#, c-format
msgid "Could not create directory '%s'"
msgstr "Konnte Verzeichnis '%s' nicht erstellen."
-#: builtin/log.c:1410
+#: builtin/log.c:1416
msgid "Failed to create output files"
msgstr "Fehler beim Erstellen der Ausgabedateien."
-#: builtin/log.c:1514
+#: builtin/log.c:1520
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
"Konnte gefolgten, externen Zweig nicht finden, bitte gebe <upstream> manuell "
"an.\n"
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
#, c-format
msgid "Unknown commit %s"
msgstr "Unbekannte Version %s"
#: builtin/merge.c:916
msgid "Empty commit message."
-msgstr "Leere Versionsbeschreibung."
+msgstr "Leere Versionsbeschreibung"
#: builtin/merge.c:928
#, c-format
#: builtin/merge.c:1298
msgid "Non-fast-forward commit does not make sense into an empty head"
-msgstr "Bin auf einem Zweig, der noch geboren wird; --no-ff macht keinen Sinn."
+msgstr "nicht vorzuspulende Version macht in einem leeren Zweig keinen Sinn"
#: builtin/merge.c:1413
#, c-format
#: builtin/mv.c:128
#, c-format
msgid "Huh? %.*s is in index?"
-msgstr "Huh? %.*s ist bereitgestellt?"
+msgstr "Huh? %.*s ist in der Bereitstellung?"
#: builtin/mv.c:140
msgid "source directory is empty"
msgid ""
"You didn't specify any refspecs to push, and push.default is \"nothing\"."
msgstr ""
-"Du hast keine Referenzspezifikationen zum Versenden angegeben, und push."
-"default ist \"nothing\"."
+"Du hast keine Referenzspezifikationen zum Versenden angegeben, und "
+"push.default ist \"nothing\"."
#: builtin/push.c:138
msgid ""
#: builtin/tag.c:372
#, c-format
msgid "The tag message has been left in %s\n"
-msgstr "Die Markierungsbeschreibung wurde in %s gelassen\n"
+msgstr "Die Markierungsbeschreibung wurde gelassen in %s\n"
#: builtin/tag.c:421
msgid "switch 'points-at' requires an object"
msgid "Updated tag '%s' (was %s)\n"
msgstr "Aktualisierte Markierung '%s' (war %s)\n"
-#: git-am.sh:49
+#: git-am.sh:50
msgid "You need to set your committer info first"
msgstr "Du musst zuerst die Informationen des Eintragenden setzen."
-#: git-am.sh:136
+#: git-am.sh:137
msgid "Repository lacks necessary blobs to fall back on 3-way merge."
msgstr ""
"Dem Projektarchiv fehlen notwendige Blobs um auf eine 3-Wege-Zusammenführung "
"zurückzufallen."
-#: git-am.sh:147
+#: git-am.sh:154
msgid ""
"Did you hand edit your patch?\n"
"It does not apply to blobs recorded in its index."
"Hast du den Patch per Hand editiert?\n"
"Er kann nicht auf die Blobs in seiner 'index' Zeile angewendet werden."
-#: git-am.sh:156
+#: git-am.sh:163
msgid "Falling back to patching base and 3-way merge..."
msgstr "Falle zurück zum Patchen der Basis und der 3-Wege-Zusammenführung..."
-#: git-am.sh:268
+#: git-am.sh:275
msgid "Only one StGIT patch series can be applied at once"
msgstr "Es kann nur eine StGIT Patch-Serie auf einmal angewendet werden."
-#: git-am.sh:355
+#: git-am.sh:362
#, sh-format
msgid "Patch format $patch_format is not supported."
msgstr "Patch-Format $patch_format wird nicht unterstützt."
-#: git-am.sh:357
+#: git-am.sh:364
msgid "Patch format detection failed."
msgstr "Patch-Formaterkennung fehlgeschlagen."
-#: git-am.sh:411
+#: git-am.sh:418
msgid "-d option is no longer supported. Do not use."
msgstr "-d Option wird nicht länger unterstützt. Nicht benutzen."
-#: git-am.sh:474
+#: git-am.sh:481
#, sh-format
msgid "previous rebase directory $dotest still exists but mbox given."
msgstr ""
"Vorheriges Verzeichnis des Neuaufbaus $dotest existiert noch, aber mbox "
"gegeben."
-#: git-am.sh:479
+#: git-am.sh:486
msgid "Please make up your mind. --skip or --abort?"
msgstr "Bitte werde dir klar. --skip oder --abort?"
-#: git-am.sh:506
+#: git-am.sh:513
msgid "Resolve operation not in progress, we are not resuming."
msgstr "Es ist keine Auflösung im Gange, es wird nicht fortgesetzt."
-#: git-am.sh:572
+#: git-am.sh:579
#, sh-format
msgid "Dirty index: cannot apply patches (dirty: $files)"
msgstr ""
"Unsaubere Bereitstellung: kann Patches nicht anwenden (unsauber: $files)"
-#: git-am.sh:748
+#: git-am.sh:755
msgid "cannot be interactive without stdin connected to a terminal."
msgstr ""
"Kann nicht interaktiv sein, ohne dass die Standard-Eingabe mit einem "
#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
#. in your translation. The program will only accept English
#. input at this point.
-#: git-am.sh:759
+#: git-am.sh:766
msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
msgstr "Anwenden? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-#: git-am.sh:795
+#: git-am.sh:802
#, sh-format
msgid "Applying: $FIRSTLINE"
msgstr "Wende an: $FIRSTLINE"
-#: git-am.sh:840
+#: git-am.sh:847
msgid "No changes -- Patch already applied."
msgstr "Keine Änderungen -- Patches bereits angewendet."
-#: git-am.sh:866
+#: git-am.sh:873
msgid "applying to an empty history"
msgstr "wende zu leerer Historie an"
#: git-bisect.sh:391
#, sh-format
msgid "cannot read $file for replaying"
-msgstr "kann $file nicht zum neu abspielen lesen"
+msgstr "kann $file nicht für das Abspielen lesen"
#: git-bisect.sh:408
msgid "?? what are you talking about?"
#: git-stash.sh:412
msgid "unable to refresh index"
-msgstr "Konnte die Bereitstellung nicht aktualisieren"
+msgstr "unfähig die Bereitstellung zu aktualisieren"
#: git-stash.sh:416
msgid "Cannot apply a stash in the middle of a merge"
msgid "cannot strip one component off url '$remoteurl'"
msgstr "Kann eine Komponente von URL '$remoteurl' nicht extrahieren"
-#: git-submodule.sh:108
+#: git-submodule.sh:109
#, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
-msgstr "Keine Unterprojekt-Zuordnung in .gitmodules für Pfad '$path' gefunden"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "Keine Unterprojekt-Zuordnung in .gitmodules für Pfad '$sm_path' gefunden"
-#: git-submodule.sh:149
+#: git-submodule.sh:150
#, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
-msgstr "Klonen von '$url' in Unterprojekt-Pfad '$path' fehlgeschlagen"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "Klonen von '$url' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
-#: git-submodule.sh:159
+#: git-submodule.sh:160
#, sh-format
msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
msgstr ""
#: git-submodule.sh:282
#, sh-format
-msgid "'$path' already exists and is not a valid git repo"
-msgstr "'$path' existiert bereits und ist kein gültiges Git-Projektarchiv"
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' existiert bereits und ist kein gültiges Git-Projektarchiv"
#: git-submodule.sh:296
#, sh-format
-msgid "Unable to checkout submodule '$path'"
-msgstr "Unfähig Unterprojekt '$path' auszuchecken"
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "Unfähig Unterprojekt '$sm_path' auszuchecken"
#: git-submodule.sh:301
#, sh-format
-msgid "Failed to add submodule '$path'"
-msgstr "Hinzufügen von Unterprojekt '$path' fehlgeschlagen"
+msgid "Failed to add submodule '$sm_path'"
+msgstr "Hinzufügen von Unterprojekt '$sm_path' fehlgeschlagen"
#: git-submodule.sh:306
#, sh-format
-msgid "Failed to register submodule '$path'"
-msgstr "Registrierung von Unterprojekt '$path' fehlgeschlagen"
+msgid "Failed to register submodule '$sm_path'"
+msgstr "Registierung von Unterprojekt '$sm_path' fehlgeschlagen"
#: git-submodule.sh:348
#, sh-format
-msgid "Entering '$prefix$path'"
-msgstr "Betrete '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
+msgstr "Betrete '$prefix$sm_path'"
#: git-submodule.sh:360
#, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
-msgstr "Stoppe bei '$path'; Skript gab nicht-Null Status zurück."
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "Stoppe bei '$sm_path'; Skript gab nicht-Null Status zurück."
#: git-submodule.sh:402
#, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
-msgstr "Keine URL für Unterprojekt-Pfad '$path' in .gitmodules gefunden"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "Keine URL für Unterprojekt-Pfad '$sm_path' in .gitmodules gefunden"
#: git-submodule.sh:411
#, sh-format
-msgid "Failed to register url for submodule path '$path'"
-msgstr "Registrierung der URL für Unterprojekt-Pfad '$path' fehlgeschlagen"
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "Registrierung der URL für Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
#: git-submodule.sh:419
#, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
-msgstr ""
-"Registrierung des Aktualisierungsmodus für Unterprojekt-Pfad '$path' "
-"fehlgeschlagen"
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr "Registrierung des Aktualisierungsmodus für Unterprojekt-Pfad "
+"'$sm_path' fehlgeschlagen"
#: git-submodule.sh:421
#, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
-msgstr "Unterprojekt '$name' ($url) ist für Pfad '$path' registriert"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "Unterprojekt '$name' ($url) ist für Pfad '$sm_path' registriert"
#: git-submodule.sh:520
#, sh-format
msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
"Maybe you want to use 'update --init'?"
msgstr ""
-"Unterprojekt-Pfad '$path' ist nicht initialisiert\n"
+"Unterprojekt-Pfad '$sm_path' ist nicht initialisiert\n"
"Vielleicht möchtest du 'update --init' benutzen?"
#: git-submodule.sh:533
#, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
-msgstr "Konnte aktuelle Version in Unterprojekt-Pfad '$path' nicht finden"
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "Konnte aktuelle Version in Unterprojekt-Pfad '$sm_path' nicht finden"
#: git-submodule.sh:552
#, sh-format
-msgid "Unable to fetch in submodule path '$path'"
-msgstr "Konnte Unterprojekt-Pfad '$path' nicht anfordern"
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "Konnte in Unterprojekt-Pfad '$sm_path' nicht anfordern"
#: git-submodule.sh:566
#, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
-msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$path' nicht möglich"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$sm_path' nicht möglich"
#: git-submodule.sh:567
#, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
-msgstr "Unterprojekt-Pfad '$path': neu aufgebaut in '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': neu aufgebaut in '$sha1'"
#: git-submodule.sh:572
#, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
msgstr ""
-"Zusammenführung von '$sha1' in Unterprojekt-Pfad '$path' fehlgeschlagen"
+"Zusammenführung von '$sha1' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
#: git-submodule.sh:573
#, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
-msgstr "Unterprojekt-Pfad '$path': zusammengeführt in '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': zusammengeführt in '$sha1'"
#: git-submodule.sh:578
#, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
-msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$path' nicht auschecken."
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$sm_path' nicht auschecken."
#: git-submodule.sh:579
#, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
-msgstr "Unterprojekt-Pfad: '$path': '$sha1' ausgecheckt"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "Unterprojekt-Pfad: '$sm_path': '$sha1' ausgecheckt"
#: git-submodule.sh:601 git-submodule.sh:924
#, sh-format
-msgid "Failed to recurse into submodule path '$path'"
-msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$sm_path'"
#: git-submodule.sh:709
msgid "--"
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-05-15 06:31+0800\n"
+"POT-Creation-Date: 2012-05-15 06:42+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"or use 'git commit -a'."
msgstr ""
-#: commit.c:47
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr ""
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr ""
+
+#: bundle.c:89 builtin/commit.c:697
+#, c-format
+msgid "could not open '%s'"
+msgstr ""
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr ""
+
+#: bundle.c:164 sequencer.c:533 sequencer.c:965 builtin/log.c:289
+#: builtin/log.c:719 builtin/log.c:1335 builtin/log.c:1554 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr ""
+
+#: bundle.c:296 builtin/log.c:1231 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr ""
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr ""
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr ""
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr ""
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr ""
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr ""
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr ""
+
+#: commit.c:48
#, c-format
msgid "could not parse %s"
msgstr ""
-#: commit.c:49
+#: commit.c:50
#, c-format
msgid "%s %s is not a commit!"
msgstr ""
msgid "failed to close rev-list's stdin: %s"
msgstr ""
+#: date.c:95
+msgid "in the future"
+msgstr ""
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] ""
+msgstr[1] ""
+
#: diff.c:105
#, c-format
msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
msgstr[0] ""
msgstr[1] ""
-#: diff.c:3439
+#: diff.c:3478
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
msgid "'%s': short read %s"
msgstr ""
-#: help.c:287
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr ""
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr ""
+
+#: help.c:270
#, c-format
msgid ""
"'%s' appears to be a git command, but we were not\n"
"able to execute it. Maybe git-%s is broken?"
msgstr ""
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr ""
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr ""
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr ""
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+msgstr[1] ""
+
#: remote.c:1607
#, c-format
msgid "Your branch is ahead of '%s' by %d commit.\n"
msgstr[0] ""
msgstr[1] ""
-#: sequencer.c:120 builtin/merge.c:865 builtin/merge.c:978
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
#: builtin/merge.c:1088 builtin/merge.c:1098
#, c-format
msgid "Could not open '%s' for writing"
msgstr ""
-#: sequencer.c:122 builtin/merge.c:333 builtin/merge.c:868
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
#: builtin/merge.c:1090 builtin/merge.c:1103
#, c-format
msgid "Could not write to '%s'"
msgstr ""
-#: sequencer.c:143
+#: sequencer.c:144
msgid ""
"after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"
msgstr ""
-#: sequencer.c:146
+#: sequencer.c:147
msgid ""
"after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'\n"
"and commit the result with 'git commit'"
msgstr ""
-#: sequencer.c:159 sequencer.c:685 sequencer.c:768
+#: sequencer.c:160 sequencer.c:741 sequencer.c:824
#, c-format
msgid "Could not write to %s"
msgstr ""
-#: sequencer.c:162
+#: sequencer.c:163
#, c-format
msgid "Error wrapping up %s"
msgstr ""
-#: sequencer.c:177
+#: sequencer.c:178
msgid "Your local changes would be overwritten by cherry-pick."
msgstr ""
-#: sequencer.c:179
+#: sequencer.c:180
msgid "Your local changes would be overwritten by revert."
msgstr ""
-#: sequencer.c:182
+#: sequencer.c:183
msgid "Commit your changes or stash them to proceed."
msgstr ""
#. TRANSLATORS: %s will be "revert" or "cherry-pick"
-#: sequencer.c:232
+#: sequencer.c:233
#, c-format
msgid "%s: Unable to write new index file"
msgstr ""
-#: sequencer.c:298
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr ""
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr ""
+
+#: sequencer.c:323
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr ""
+
+#: sequencer.c:328
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr ""
+
+#: sequencer.c:358
msgid "Your index file is unmerged."
msgstr ""
-#: sequencer.c:301
+#: sequencer.c:361
msgid "You do not have a valid HEAD"
msgstr ""
-#: sequencer.c:316
+#: sequencer.c:376
#, c-format
msgid "Commit %s is a merge but no -m option was given."
msgstr ""
-#: sequencer.c:324
+#: sequencer.c:384
#, c-format
msgid "Commit %s does not have parent %d"
msgstr ""
-#: sequencer.c:328
+#: sequencer.c:388
#, c-format
msgid "Mainline was specified but commit %s is not a merge."
msgstr ""
#. TRANSLATORS: The first %s will be "revert" or
#. "cherry-pick", the second %s a SHA1
-#: sequencer.c:339
+#: sequencer.c:399
#, c-format
msgid "%s: cannot parse parent commit %s"
msgstr ""
-#: sequencer.c:343
+#: sequencer.c:403
#, c-format
msgid "Cannot get commit message for %s"
msgstr ""
-#: sequencer.c:427
+#: sequencer.c:491
#, c-format
msgid "could not revert %s... %s"
msgstr ""
-#: sequencer.c:428
+#: sequencer.c:492
#, c-format
msgid "could not apply %s... %s"
msgstr ""
-#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
-#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:347
-#: builtin/shortlog.c:181
-msgid "revision walk setup failed"
-msgstr ""
-
-#: sequencer.c:453
+#: sequencer.c:536
msgid "empty commit set passed"
msgstr ""
-#: sequencer.c:461
+#: sequencer.c:544
#, c-format
msgid "git %s: failed to read the index"
msgstr ""
-#: sequencer.c:466
+#: sequencer.c:549
#, c-format
msgid "git %s: failed to refresh the index"
msgstr ""
-#: sequencer.c:551
+#: sequencer.c:607
#, c-format
msgid "Cannot %s during a %s"
msgstr ""
-#: sequencer.c:573
+#: sequencer.c:629
#, c-format
msgid "Could not parse line %d."
msgstr ""
-#: sequencer.c:578
+#: sequencer.c:634
msgid "No commits parsed."
msgstr ""
-#: sequencer.c:591
+#: sequencer.c:647
#, c-format
msgid "Could not open %s"
msgstr ""
-#: sequencer.c:595
+#: sequencer.c:651
#, c-format
msgid "Could not read %s."
msgstr ""
-#: sequencer.c:602
+#: sequencer.c:658
#, c-format
msgid "Unusable instruction sheet: %s"
msgstr ""
-#: sequencer.c:630
+#: sequencer.c:686
#, c-format
msgid "Invalid key: %s"
msgstr ""
-#: sequencer.c:633
+#: sequencer.c:689
#, c-format
msgid "Invalid value for %s: %s"
msgstr ""
-#: sequencer.c:645
+#: sequencer.c:701
#, c-format
msgid "Malformed options sheet: %s"
msgstr ""
-#: sequencer.c:666
+#: sequencer.c:722
msgid "a cherry-pick or revert is already in progress"
msgstr ""
-#: sequencer.c:667
+#: sequencer.c:723
msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
msgstr ""
-#: sequencer.c:671
+#: sequencer.c:727
#, c-format
msgid "Could not create sequencer directory %s"
msgstr ""
-#: sequencer.c:687 sequencer.c:772
+#: sequencer.c:743 sequencer.c:828
#, c-format
msgid "Error wrapping up %s."
msgstr ""
-#: sequencer.c:706 sequencer.c:840
+#: sequencer.c:762 sequencer.c:896
msgid "no cherry-pick or revert in progress"
msgstr ""
-#: sequencer.c:708
+#: sequencer.c:764
msgid "cannot resolve HEAD"
msgstr ""
-#: sequencer.c:710
+#: sequencer.c:766
msgid "cannot abort from a branch yet to be born"
msgstr ""
-#: sequencer.c:732
+#: sequencer.c:788 builtin/apply.c:3689
#, c-format
msgid "cannot open %s: %s"
msgstr ""
-#: sequencer.c:735
+#: sequencer.c:791
#, c-format
msgid "cannot read %s: %s"
msgstr ""
-#: sequencer.c:736
+#: sequencer.c:792
msgid "unexpected end of file"
msgstr ""
-#: sequencer.c:742
+#: sequencer.c:798
#, c-format
msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
msgstr ""
-#: sequencer.c:765
+#: sequencer.c:821
#, c-format
msgid "Could not format %s."
msgstr ""
-#: sequencer.c:927
+#: sequencer.c:983
msgid "Can't revert as initial commit"
msgstr ""
-#: sequencer.c:928
+#: sequencer.c:984
msgid "Can't cherry-pick into empty head"
msgstr ""
-#: wt-status.c:134
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr ""
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr ""
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr ""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
+#: wt-status.c:135
msgid "Unmerged paths:"
msgstr ""
-#: wt-status.c:140 wt-status.c:157
+#: wt-status.c:141 wt-status.c:158
#, c-format
msgid " (use \"git reset %s <file>...\" to unstage)"
msgstr ""
-#: wt-status.c:142 wt-status.c:159
+#: wt-status.c:143 wt-status.c:160
msgid " (use \"git rm --cached <file>...\" to unstage)"
msgstr ""
-#: wt-status.c:143
+#: wt-status.c:144
msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
msgstr ""
-#: wt-status.c:151
+#: wt-status.c:152
msgid "Changes to be committed:"
msgstr ""
-#: wt-status.c:169
+#: wt-status.c:170
msgid "Changes not staged for commit:"
msgstr ""
-#: wt-status.c:173
+#: wt-status.c:174
msgid " (use \"git add <file>...\" to update what will be committed)"
msgstr ""
-#: wt-status.c:175
+#: wt-status.c:176
msgid " (use \"git add/rm <file>...\" to update what will be committed)"
msgstr ""
-#: wt-status.c:176
+#: wt-status.c:177
msgid ""
" (use \"git checkout -- <file>...\" to discard changes in working directory)"
msgstr ""
-#: wt-status.c:178
+#: wt-status.c:179
msgid " (commit or discard the untracked or modified content in submodules)"
msgstr ""
-#: wt-status.c:187
+#: wt-status.c:188
#, c-format
msgid "%s files:"
msgstr ""
-#: wt-status.c:190
+#: wt-status.c:191
#, c-format
msgid " (use \"git %s <file>...\" to include in what will be committed)"
msgstr ""
-#: wt-status.c:207
+#: wt-status.c:208
msgid "bug"
msgstr ""
-#: wt-status.c:212
+#: wt-status.c:213
msgid "both deleted:"
msgstr ""
-#: wt-status.c:213
+#: wt-status.c:214
msgid "added by us:"
msgstr ""
-#: wt-status.c:214
+#: wt-status.c:215
msgid "deleted by them:"
msgstr ""
-#: wt-status.c:215
+#: wt-status.c:216
msgid "added by them:"
msgstr ""
-#: wt-status.c:216
+#: wt-status.c:217
msgid "deleted by us:"
msgstr ""
-#: wt-status.c:217
+#: wt-status.c:218
msgid "both added:"
msgstr ""
-#: wt-status.c:218
+#: wt-status.c:219
msgid "both modified:"
msgstr ""
-#: wt-status.c:248
+#: wt-status.c:249
msgid "new commits, "
msgstr ""
-#: wt-status.c:250
+#: wt-status.c:251
msgid "modified content, "
msgstr ""
-#: wt-status.c:252
+#: wt-status.c:253
msgid "untracked content, "
msgstr ""
-#: wt-status.c:266
+#: wt-status.c:267
#, c-format
msgid "new file: %s"
msgstr ""
-#: wt-status.c:269
+#: wt-status.c:270
#, c-format
msgid "copied: %s -> %s"
msgstr ""
-#: wt-status.c:272
+#: wt-status.c:273
#, c-format
msgid "deleted: %s"
msgstr ""
-#: wt-status.c:275
+#: wt-status.c:276
#, c-format
msgid "modified: %s"
msgstr ""
-#: wt-status.c:278
+#: wt-status.c:279
#, c-format
msgid "renamed: %s -> %s"
msgstr ""
-#: wt-status.c:281
+#: wt-status.c:282
#, c-format
msgid "typechange: %s"
msgstr ""
-#: wt-status.c:284
+#: wt-status.c:285
#, c-format
msgid "unknown: %s"
msgstr ""
-#: wt-status.c:287
+#: wt-status.c:288
#, c-format
msgid "unmerged: %s"
msgstr ""
-#: wt-status.c:290
+#: wt-status.c:291
#, c-format
msgid "bug: unhandled diff status %c"
msgstr ""
-#: wt-status.c:713
+#: wt-status.c:737
msgid "On branch "
msgstr ""
-#: wt-status.c:720
+#: wt-status.c:744
msgid "Not currently on any branch."
msgstr ""
-#: wt-status.c:731
+#: wt-status.c:755
msgid "Initial commit"
msgstr ""
-#: wt-status.c:745
+#: wt-status.c:769
msgid "Untracked"
msgstr ""
-#: wt-status.c:747
+#: wt-status.c:771
msgid "Ignored"
msgstr ""
-#: wt-status.c:749
+#: wt-status.c:773
#, c-format
msgid "Untracked files not listed%s"
msgstr ""
-#: wt-status.c:751
+#: wt-status.c:775
msgid " (use -u option to show untracked files)"
msgstr ""
-#: wt-status.c:757
+#: wt-status.c:781
msgid "No changes"
msgstr ""
-#: wt-status.c:761
+#: wt-status.c:785
#, c-format
msgid "no changes added to commit%s\n"
msgstr ""
-#: wt-status.c:763
+#: wt-status.c:787
msgid " (use \"git add\" and/or \"git commit -a\")"
msgstr ""
-#: wt-status.c:765
+#: wt-status.c:789
#, c-format
msgid "nothing added to commit but untracked files present%s\n"
msgstr ""
-#: wt-status.c:767
+#: wt-status.c:791
msgid " (use \"git add\" to track)"
msgstr ""
-#: wt-status.c:769 wt-status.c:772 wt-status.c:775
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
#, c-format
msgid "nothing to commit%s\n"
msgstr ""
-#: wt-status.c:770
+#: wt-status.c:794
msgid " (create/copy files and use \"git add\" to track)"
msgstr ""
-#: wt-status.c:773
+#: wt-status.c:797
msgid " (use -u to show untracked files)"
msgstr ""
-#: wt-status.c:776
+#: wt-status.c:800
msgid " (working directory clean)"
msgstr ""
-#: wt-status.c:884
+#: wt-status.c:908
msgid "HEAD (no branch)"
msgstr ""
-#: wt-status.c:890
+#: wt-status.c:914
msgid "Initial commit on "
msgstr ""
-#: wt-status.c:905
+#: wt-status.c:929
msgid "behind "
msgstr ""
-#: wt-status.c:908 wt-status.c:911
+#: wt-status.c:932 wt-status.c:935
msgid "ahead "
msgstr ""
-#: wt-status.c:913
+#: wt-status.c:937
msgid ", behind "
msgstr ""
msgid "unexpected diff status %c"
msgstr ""
-#: builtin/add.c:67 builtin/commit.c:298
+#: builtin/add.c:67 builtin/commit.c:226
msgid "updating files failed"
msgstr ""
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr ""
-#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:358 builtin/mv.c:82
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
#: builtin/rm.c:162
msgid "index file corrupt"
msgstr ""
-#: builtin/add.c:476 builtin/mv.c:229 builtin/rm.c:260
+#: builtin/add.c:476 builtin/apply.c:4100 builtin/mv.c:229 builtin/rm.c:260
msgid "Unable to write new index file"
msgstr ""
-#: builtin/archive.c:17
+#: builtin/apply.c:106
#, c-format
-msgid "could not create archive file '%s'"
+msgid "unrecognized whitespace option '%s'"
msgstr ""
-#: builtin/archive.c:20
-msgid "could not redirect output"
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
msgstr ""
-#: builtin/archive.c:37
-msgid "git archive: Remote with no URL"
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
msgstr ""
-#: builtin/archive.c:58
-msgid "git archive: expected ACK/NAK, got EOF"
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
msgstr ""
-#: builtin/archive.c:63
+#: builtin/apply.c:905
#, c-format
-msgid "git archive: NACK %s"
+msgid "unable to find filename in patch at line %d"
msgstr ""
-#: builtin/archive.c:65
+#: builtin/apply.c:937
#, c-format
-msgid "remote error: %s"
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
msgstr ""
-#: builtin/archive.c:66
-msgid "git archive: protocol error"
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
msgstr ""
-#: builtin/archive.c:71
-msgid "git archive: expected a flush"
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
msgstr ""
-#: builtin/branch.c:137
+#: builtin/apply.c:949
#, c-format
-msgid ""
-"deleting branch '%s' that has been merged to\n"
-" '%s', but not yet merged to HEAD."
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
msgstr ""
-#: builtin/branch.c:141
+#: builtin/apply.c:1394
#, c-format
-msgid ""
-"not deleting branch '%s' that is not yet merged to\n"
-" '%s', even though it is merged to HEAD."
+msgid "recount: unexpected line: %.*s"
msgstr ""
-#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
-msgid "remote "
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
msgstr ""
-#: builtin/branch.c:171
-msgid "cannot use -a with -d"
-msgstr ""
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] ""
+msgstr[1] ""
-#: builtin/branch.c:177
-msgid "Couldn't look up commit object for HEAD"
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
msgstr ""
-#: builtin/branch.c:182
-#, c-format
-msgid "Cannot delete the branch '%s' which you are currently on."
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
msgstr ""
-#: builtin/branch.c:192
+#: builtin/apply.c:1656
#, c-format
-msgid "%sbranch '%s' not found."
+msgid "corrupt patch at line %d"
msgstr ""
-#: builtin/branch.c:200
+#: builtin/apply.c:1692
#, c-format
-msgid "Couldn't look up commit object for '%s'"
+msgid "new file %s depends on old contents"
msgstr ""
-#: builtin/branch.c:206
+#: builtin/apply.c:1694
#, c-format
-msgid ""
-"The branch '%s' is not fully merged.\n"
-"If you are sure you want to delete it, run 'git branch -D %s'."
+msgid "deleted file %s still has contents"
msgstr ""
-#: builtin/branch.c:214
+#: builtin/apply.c:1697
#, c-format
-msgid "Error deleting %sbranch '%s'"
+msgid "** warning: file %s becomes empty but is not deleted"
msgstr ""
-#: builtin/branch.c:219
+#: builtin/apply.c:1843
#, c-format
-msgid "Deleted %sbranch %s (was %s).\n"
-msgstr ""
-
-#: builtin/branch.c:224
-msgid "Update of config-file failed"
+msgid "corrupt binary patch at line %d: %.*s"
msgstr ""
-#: builtin/branch.c:322
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
#, c-format
-msgid "branch '%s' does not point at a commit"
+msgid "unrecognized binary patch at line %d"
msgstr ""
-#: builtin/branch.c:394
+#: builtin/apply.c:1958
#, c-format
-msgid "behind %d] "
+msgid "patch with only garbage at line %d"
msgstr ""
-#: builtin/branch.c:396
+#: builtin/apply.c:2048
#, c-format
-msgid "ahead %d] "
+msgid "unable to read symlink %s"
msgstr ""
-#: builtin/branch.c:398
+#: builtin/apply.c:2052
#, c-format
-msgid "ahead %d, behind %d] "
+msgid "unable to open or read %s"
msgstr ""
-#: builtin/branch.c:501
-msgid "(no branch)"
+#: builtin/apply.c:2123
+msgid "oops"
msgstr ""
-#: builtin/branch.c:566
-msgid "some refs could not be read"
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
msgstr ""
-#: builtin/branch.c:579
-msgid "cannot rename the current branch while not on any."
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
msgstr ""
-#: builtin/branch.c:589
+#: builtin/apply.c:2781
#, c-format
-msgid "Invalid branch name: '%s'"
+msgid ""
+"while searching for:\n"
+"%.*s"
msgstr ""
-#: builtin/branch.c:604
-msgid "Branch rename failed"
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
msgstr ""
-#: builtin/branch.c:608
+#: builtin/apply.c:2903
#, c-format
-msgid "Renamed a misnamed branch '%s' away"
+msgid "binary patch does not apply to '%s'"
msgstr ""
-#: builtin/branch.c:612
+#: builtin/apply.c:2909
#, c-format
-msgid "Branch renamed to %s, but HEAD is not updated!"
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
msgstr ""
-#: builtin/branch.c:619
-msgid "Branch is renamed, but update of config-file failed"
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
msgstr ""
-#: builtin/branch.c:634
+#: builtin/apply.c:3045
#, c-format
-msgid "malformed object name %s"
+msgid "patch %s has been renamed/deleted"
msgstr ""
-#: builtin/branch.c:658
+#: builtin/apply.c:3052 builtin/apply.c:3069
#, c-format
-msgid "could not write branch description template: %s\n"
+msgid "read of %s failed"
msgstr ""
-#: builtin/branch.c:746
-msgid "Failed to resolve HEAD as a valid ref."
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
msgstr ""
-#: builtin/branch.c:751 builtin/clone.c:558
-msgid "HEAD not found below refs/heads!"
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
msgstr ""
-#: builtin/branch.c:809
-msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
msgstr ""
-#: builtin/bundle.c:47
+#: builtin/apply.c:3148 builtin/apply.c:3179
#, c-format
-msgid "%s is okay\n"
+msgid "%s: %s"
msgstr ""
-#: builtin/bundle.c:56
-msgid "Need a repository to create a bundle."
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
msgstr ""
-#: builtin/bundle.c:60
-msgid "Need a repository to unbundle."
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
msgstr ""
-#: builtin/checkout.c:113 builtin/checkout.c:146
+#: builtin/apply.c:3190
#, c-format
-msgid "path '%s' does not have our version"
+msgid "%s: wrong type"
msgstr ""
-#: builtin/checkout.c:115 builtin/checkout.c:148
+#: builtin/apply.c:3192
#, c-format
-msgid "path '%s' does not have their version"
+msgid "%s has type %o, expected %o"
msgstr ""
-#: builtin/checkout.c:131
+#: builtin/apply.c:3247
#, c-format
-msgid "path '%s' does not have all necessary versions"
+msgid "%s: already exists in index"
msgstr ""
-#: builtin/checkout.c:175
+#: builtin/apply.c:3266
#, c-format
-msgid "path '%s' does not have necessary versions"
+msgid "new mode (%o) of %s does not match old mode (%o)%s%s"
msgstr ""
-#: builtin/checkout.c:192
+#: builtin/apply.c:3272
#, c-format
-msgid "path '%s': cannot merge"
+msgid "%s: patch does not apply"
msgstr ""
-#: builtin/checkout.c:209
+#: builtin/apply.c:3285
#, c-format
-msgid "Unable to add merge result for '%s'"
+msgid "Checking patch %s..."
msgstr ""
-#: builtin/checkout.c:212 builtin/reset.c:158
+#: builtin/apply.c:3340 builtin/checkout.c:212 builtin/reset.c:158
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr ""
-#: builtin/checkout.c:234 builtin/checkout.c:392
-msgid "corrupt index file"
+#: builtin/apply.c:3483
+#, c-format
+msgid "unable to remove %s from index"
msgstr ""
-#: builtin/checkout.c:264 builtin/checkout.c:271
+#: builtin/apply.c:3510
#, c-format
-msgid "path '%s' is unmerged"
+msgid "corrupt patch for subproject %s"
msgstr ""
-#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
-#: builtin/merge.c:812
-msgid "unable to write new index file"
+#: builtin/apply.c:3514
+#, c-format
+msgid "unable to stat newly created file '%s'"
msgstr ""
-#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
-msgid "diff_setup_done failed"
+#: builtin/apply.c:3519
+#, c-format
+msgid "unable to create backing store for newly created file %s"
msgstr ""
-#: builtin/checkout.c:414
-msgid "you need to resolve your current index first"
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to add cache entry for %s"
msgstr ""
-#: builtin/checkout.c:533
+#: builtin/apply.c:3555
#, c-format
-msgid "Can not do reflog for '%s'\n"
+msgid "closing file '%s'"
msgstr ""
-#: builtin/checkout.c:565
-msgid "HEAD is now at"
-msgstr ""
-
-#: builtin/checkout.c:572
+#: builtin/apply.c:3604
#, c-format
-msgid "Reset branch '%s'\n"
+msgid "unable to write file '%s' mode %o"
msgstr ""
-#: builtin/checkout.c:575
+#: builtin/apply.c:3660
#, c-format
-msgid "Already on '%s'\n"
+msgid "Applied patch %s cleanly."
msgstr ""
-#: builtin/checkout.c:579
-#, c-format
-msgid "Switched to and reset branch '%s'\n"
+#: builtin/apply.c:3668
+msgid "internal error"
msgstr ""
-#: builtin/checkout.c:581
+#. Say this even without --verbose
+#: builtin/apply.c:3671
#, c-format
-msgid "Switched to a new branch '%s'\n"
-msgstr ""
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] ""
+msgstr[1] ""
-#: builtin/checkout.c:583
+#: builtin/apply.c:3681
#, c-format
-msgid "Switched to branch '%s'\n"
+msgid "truncating .rej filename to %.*s.rej"
msgstr ""
-#: builtin/checkout.c:639
+#: builtin/apply.c:3702
#, c-format
-msgid " ... and %d more.\n"
+msgid "Hunk #%d applied cleanly."
msgstr ""
-#. The singular version
-#: builtin/checkout.c:645
-#, c-format
-msgid ""
-"Warning: you are leaving %d commit behind, not connected to\n"
-"any of your branches:\n"
-"\n"
-"%s\n"
-msgid_plural ""
-"Warning: you are leaving %d commits behind, not connected to\n"
-"any of your branches:\n"
-"\n"
-"%s\n"
-msgstr[0] ""
-msgstr[1] ""
-
-#: builtin/checkout.c:663
+#: builtin/apply.c:3705
#, c-format
-msgid ""
-"If you want to keep them by creating a new branch, this may be a good time\n"
-"to do so with:\n"
-"\n"
-" git branch new_branch_name %s\n"
-"\n"
+msgid "Rejected hunk #%d."
msgstr ""
-#: builtin/checkout.c:693
-msgid "internal error in revision walk"
+#: builtin/apply.c:3836
+msgid "unrecognized input"
msgstr ""
-#: builtin/checkout.c:697
-msgid "Previous HEAD position was"
+#: builtin/apply.c:3847
+msgid "unable to read index file"
msgstr ""
-#: builtin/checkout.c:723
-msgid "You are on a branch yet to be born"
+#: builtin/apply.c:4042
+msgid "--index outside a repository"
msgstr ""
-#. case (1)
-#: builtin/checkout.c:854
-#, c-format
-msgid "invalid reference: %s"
+#: builtin/apply.c:4045
+msgid "--cached outside a repository"
msgstr ""
-#. case (1): want a tree
-#: builtin/checkout.c:893
+#: builtin/apply.c:4061
#, c-format
-msgid "reference is not a tree: %s"
+msgid "can't open patch '%s'"
msgstr ""
-#: builtin/checkout.c:973
-msgid "-B cannot be used with -b"
-msgstr ""
+#: builtin/apply.c:4075
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] ""
+msgstr[1] ""
-#: builtin/checkout.c:982
-msgid "--patch is incompatible with all other options"
-msgstr ""
+#: builtin/apply.c:4081 builtin/apply.c:4091
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] ""
+msgstr[1] ""
-#: builtin/checkout.c:985
-msgid "--detach cannot be used with -b/-B/--orphan"
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
msgstr ""
-#: builtin/checkout.c:987
-msgid "--detach cannot be used with -t"
+#: builtin/archive.c:20
+msgid "could not redirect output"
msgstr ""
-#: builtin/checkout.c:993
-msgid "--track needs a branch name"
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
msgstr ""
-#: builtin/checkout.c:1000
-msgid "Missing branch name; try -b"
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
msgstr ""
-#: builtin/checkout.c:1006
-msgid "--orphan and -b|-B are mutually exclusive"
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
msgstr ""
-#: builtin/checkout.c:1008
-msgid "--orphan cannot be used with -t"
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
msgstr ""
-#: builtin/checkout.c:1018
-msgid "git checkout: -f and -m are incompatible"
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
msgstr ""
-#: builtin/checkout.c:1052
-msgid "invalid path specification"
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
msgstr ""
-#: builtin/checkout.c:1060
+#: builtin/branch.c:144
#, c-format
msgid ""
-"git checkout: updating paths is incompatible with switching branches.\n"
-"Did you intend to checkout '%s' which can not be resolved as commit?"
-msgstr ""
-
-#: builtin/checkout.c:1062
-msgid "git checkout: updating paths is incompatible with switching branches."
-msgstr ""
-
-#: builtin/checkout.c:1067
-msgid "git checkout: --detach does not take a path argument"
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
msgstr ""
-#: builtin/checkout.c:1070
+#: builtin/branch.c:148
+#, c-format
msgid ""
-"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
-"checking out of the index."
-msgstr ""
-
-#: builtin/checkout.c:1089
-msgid "Cannot switch branch to a non-commit."
-msgstr ""
-
-#: builtin/checkout.c:1092
-msgid "--ours/--theirs is incompatible with switching branches."
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
msgstr ""
-#: builtin/clean.c:78
-msgid "-x and -X cannot be used together"
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
msgstr ""
-#: builtin/clean.c:82
-msgid ""
-"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
msgstr ""
-#: builtin/clean.c:85
-msgid ""
-"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
-"clean"
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
msgstr ""
-#: builtin/clean.c:155 builtin/clean.c:176
+#: builtin/branch.c:202
#, c-format
-msgid "Would remove %s\n"
+msgid "remote branch '%s' not found."
msgstr ""
-#: builtin/clean.c:159 builtin/clean.c:179
+#: builtin/branch.c:203
#, c-format
-msgid "Removing %s\n"
+msgid "branch '%s' not found."
msgstr ""
-#: builtin/clean.c:162 builtin/clean.c:182
+#: builtin/branch.c:210
#, c-format
-msgid "failed to remove %s"
+msgid "Couldn't look up commit object for '%s'"
msgstr ""
-#: builtin/clean.c:166
+#: builtin/branch.c:216
#, c-format
-msgid "Would not remove %s\n"
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
msgstr ""
-#: builtin/clean.c:168
+#: builtin/branch.c:225
#, c-format
-msgid "Not removing %s\n"
+msgid "Error deleting remote branch '%s'"
msgstr ""
-#: builtin/clone.c:243
+#: builtin/branch.c:226
#, c-format
-msgid "reference repository '%s' is not a local directory."
+msgid "Error deleting branch '%s'"
msgstr ""
-#: builtin/clone.c:302
+#: builtin/branch.c:233
#, c-format
-msgid "failed to open '%s'"
+msgid "Deleted remote branch %s (was %s).\n"
msgstr ""
-#: builtin/clone.c:306
+#: builtin/branch.c:234
#, c-format
-msgid "failed to create directory '%s'"
+msgid "Deleted branch %s (was %s).\n"
msgstr ""
-#: builtin/clone.c:308 builtin/diff.c:75
-#, c-format
-msgid "failed to stat '%s'"
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
msgstr ""
-#: builtin/clone.c:310
+#: builtin/branch.c:337
#, c-format
-msgid "%s exists and is not a directory"
+msgid "branch '%s' does not point at a commit"
msgstr ""
-#: builtin/clone.c:324
+#: builtin/branch.c:409
#, c-format
-msgid "failed to stat %s\n"
+msgid "[%s: behind %d]"
msgstr ""
-#: builtin/clone.c:341
+#: builtin/branch.c:411
#, c-format
-msgid "failed to unlink '%s'"
+msgid "[behind %d]"
msgstr ""
-#: builtin/clone.c:346
+#: builtin/branch.c:415
#, c-format
-msgid "failed to create link '%s'"
+msgid "[%s: ahead %d]"
msgstr ""
-#: builtin/clone.c:350
+#: builtin/branch.c:417
#, c-format
-msgid "failed to copy file to '%s'"
+msgid "[ahead %d]"
msgstr ""
-#: builtin/clone.c:373
+#: builtin/branch.c:420
#, c-format
-msgid "done.\n"
+msgid "[%s: ahead %d, behind %d]"
msgstr ""
-#: builtin/clone.c:440
+#: builtin/branch.c:423
#, c-format
-msgid "Could not find remote branch %s to clone."
+msgid "[ahead %d, behind %d]"
msgstr ""
-#: builtin/clone.c:549
-msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+#: builtin/branch.c:535
+msgid "(no branch)"
msgstr ""
-#: builtin/clone.c:639
-msgid "Too many arguments."
+#: builtin/branch.c:600
+msgid "some refs could not be read"
msgstr ""
-#: builtin/clone.c:643
-msgid "You must specify a repository to clone."
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
msgstr ""
-#: builtin/clone.c:654
+#: builtin/branch.c:623
#, c-format
-msgid "--bare and --origin %s options are incompatible."
+msgid "Invalid branch name: '%s'"
msgstr ""
-#: builtin/clone.c:668
-#, c-format
-msgid "repository '%s' does not exist"
+#: builtin/branch.c:638
+msgid "Branch rename failed"
msgstr ""
-#: builtin/clone.c:673
-msgid "--depth is ignored in local clones; use file:// instead."
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
msgstr ""
-#: builtin/clone.c:683
+#: builtin/branch.c:646
#, c-format
-msgid "destination path '%s' already exists and is not an empty directory."
+msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr ""
-#: builtin/clone.c:693
-#, c-format
-msgid "working tree '%s' already exists."
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
msgstr ""
-#: builtin/clone.c:706 builtin/clone.c:720
+#: builtin/branch.c:668
#, c-format
-msgid "could not create leading directories of '%s'"
+msgid "malformed object name %s"
msgstr ""
-#: builtin/clone.c:709
+#: builtin/branch.c:692
#, c-format
-msgid "could not create work tree dir '%s'."
+msgid "could not write branch description template: %s"
msgstr ""
-#: builtin/clone.c:728
-#, c-format
-msgid "Cloning into bare repository '%s'...\n"
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
msgstr ""
-#: builtin/clone.c:730
-#, c-format
-msgid "Cloning into '%s'...\n"
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
msgstr ""
-#: builtin/clone.c:786
-#, c-format
-msgid "Don't know how to clone %s"
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
msgstr ""
-#: builtin/clone.c:835
-#, c-format
-msgid "Remote branch %s not found in upstream %s"
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
-#: builtin/clone.c:842
-msgid "You appear to have cloned an empty repository."
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
msgstr ""
-#: builtin/commit.c:42
-msgid ""
-"Your name and email address were configured automatically based\n"
-"on your username and hostname. Please check that they are accurate.\n"
-"You can suppress this message by setting them explicitly:\n"
-"\n"
-" git config --global user.name \"Your Name\"\n"
-" git config --global user.email you@example.com\n"
-"\n"
-"After doing this, you may fix the identity used for this commit with:\n"
-"\n"
-" git commit --amend --reset-author\n"
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
msgstr ""
-#: builtin/commit.c:54
-msgid ""
-"You asked to amend the most recent commit, but doing so would make\n"
-"it empty. You can repeat your command with --allow-empty, or you can\n"
-"remove the commit entirely with \"git reset HEAD^\".\n"
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
msgstr ""
-#: builtin/commit.c:59
-msgid ""
-"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
-"If you wish to commit it anyway, use:\n"
-"\n"
-" git commit --allow-empty\n"
-"\n"
-"Otherwise, please use 'git reset'\n"
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
msgstr ""
-#: builtin/commit.c:205 builtin/reset.c:33
-msgid "merge"
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
msgstr ""
-#: builtin/commit.c:208
-msgid "cherry-pick"
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
msgstr ""
-#: builtin/commit.c:325
-msgid "failed to unpack HEAD tree object"
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
msgstr ""
-#: builtin/commit.c:367
-msgid "unable to create temporary index"
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
msgstr ""
-#: builtin/commit.c:373
-msgid "interactive add failed"
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
msgstr ""
-#: builtin/commit.c:406 builtin/commit.c:427 builtin/commit.c:473
-msgid "unable to write new_index file"
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
msgstr ""
-#: builtin/commit.c:457
+#: builtin/checkout.c:264 builtin/checkout.c:271
#, c-format
-msgid "cannot do a partial commit during a %s."
+msgid "path '%s' is unmerged"
msgstr ""
-#: builtin/commit.c:466
-msgid "cannot read the index"
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
msgstr ""
-#: builtin/commit.c:486
-msgid "unable to write temporary index file"
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr ""
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
msgstr ""
-#: builtin/commit.c:561 builtin/commit.c:567
+#: builtin/checkout.c:533
#, c-format
-msgid "invalid commit: %s"
+msgid "Can not do reflog for '%s'\n"
msgstr ""
-#: builtin/commit.c:590
-msgid "malformed --author parameter"
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
msgstr ""
-#: builtin/commit.c:651
+#: builtin/checkout.c:573
#, c-format
-msgid "Malformed ident string: '%s'"
+msgid "Reset branch '%s'\n"
msgstr ""
-#: builtin/commit.c:689 builtin/commit.c:722 builtin/commit.c:1033
+#: builtin/checkout.c:576
#, c-format
-msgid "could not lookup commit %s"
+msgid "Already on '%s'\n"
msgstr ""
-#: builtin/commit.c:701 builtin/shortlog.c:296
+#: builtin/checkout.c:580
#, c-format
-msgid "(reading log message from standard input)\n"
+msgid "Switched to and reset branch '%s'\n"
msgstr ""
-#: builtin/commit.c:703
-msgid "could not read log from standard input"
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
msgstr ""
-#: builtin/commit.c:707
+#: builtin/checkout.c:584
#, c-format
-msgid "could not read log file '%s'"
+msgid "Switched to branch '%s'\n"
msgstr ""
-#: builtin/commit.c:713
-msgid "commit has empty message"
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
msgstr ""
-#: builtin/commit.c:729
-msgid "could not read MERGE_MSG"
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
msgstr ""
-#: builtin/commit.c:733
-msgid "could not read SQUASH_MSG"
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
msgstr ""
-#: builtin/commit.c:737
-#, c-format
-msgid "could not read '%s'"
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
msgstr ""
-#: builtin/commit.c:765
-#, c-format
-msgid "could not open '%s'"
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
msgstr ""
-#: builtin/commit.c:789
-msgid "could not write commit template"
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
msgstr ""
-#: builtin/commit.c:799
+#. case (1): want a tree
+#: builtin/checkout.c:894
#, c-format
-msgid ""
-"\n"
-"It looks like you may be committing a %s.\n"
-"If this is not correct, please remove the file\n"
-"\t%s\n"
-"and try again.\n"
+msgid "reference is not a tree: %s"
msgstr ""
-#: builtin/commit.c:812
-msgid "Please enter the commit message for your changes."
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
msgstr ""
-#: builtin/commit.c:815
-msgid ""
-" Lines starting\n"
-"with '#' will be ignored, and an empty message aborts the commit.\n"
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
msgstr ""
-#: builtin/commit.c:820
-msgid ""
-" Lines starting\n"
-"with '#' will be kept; you may remove them yourself if you want to.\n"
-"An empty message aborts the commit.\n"
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
msgstr ""
-#: builtin/commit.c:832
-#, c-format
-msgid "%sAuthor: %s"
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
msgstr ""
-#: builtin/commit.c:839
-#, c-format
-msgid "%sCommitter: %s"
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
msgstr ""
-#: builtin/commit.c:859
-msgid "Cannot read index"
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
msgstr ""
-#: builtin/commit.c:896
-msgid "Error building trees"
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
msgstr ""
-#: builtin/commit.c:911 builtin/tag.c:357
-#, c-format
-msgid "Please supply the message using either -m or -F option.\n"
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
msgstr ""
-#: builtin/commit.c:1008
-#, c-format
-msgid "No existing author found with '%s'"
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
msgstr ""
-#: builtin/commit.c:1023 builtin/commit.c:1217
-#, c-format
-msgid "Invalid untracked files mode '%s'"
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
msgstr ""
-#: builtin/commit.c:1063
-msgid "Using both --reset-author and --author does not make sense"
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
msgstr ""
-#: builtin/commit.c:1074
-msgid "You have nothing to amend."
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
msgstr ""
-#: builtin/commit.c:1076
-#, c-format
-msgid "You are in the middle of a %s -- cannot amend."
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
msgstr ""
-#: builtin/commit.c:1078
-msgid "Options --squash and --fixup cannot be used together"
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
msgstr ""
-#: builtin/commit.c:1088
-msgid "Only one of -c/-C/-F/--fixup can be used."
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
msgstr ""
-#: builtin/commit.c:1090
-msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
msgstr ""
-#: builtin/commit.c:1098
-msgid "--reset-author can be used only with -C, -c or --amend."
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
msgstr ""
-#: builtin/commit.c:1115
-msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
msgstr ""
-#: builtin/commit.c:1117
-msgid "No paths with --include/--only does not make sense."
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
msgstr ""
-#: builtin/commit.c:1119
-msgid "Clever... amending the last one with dirty index."
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
msgstr ""
-#: builtin/commit.c:1121
-msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
msgstr ""
-#: builtin/commit.c:1131 builtin/tag.c:556
+#: builtin/clean.c:162 builtin/clean.c:182
#, c-format
-msgid "Invalid cleanup mode %s"
+msgid "failed to remove %s"
msgstr ""
-#: builtin/commit.c:1136
-msgid "Paths with -a does not make sense."
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
msgstr ""
-#: builtin/commit.c:1315
-msgid "couldn't look up newly created commit"
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
msgstr ""
-#: builtin/commit.c:1317
-msgid "could not parse newly created commit"
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
msgstr ""
-#: builtin/commit.c:1358
-msgid "detached HEAD"
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
msgstr ""
-#: builtin/commit.c:1360
-msgid " (root-commit)"
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
msgstr ""
-#: builtin/commit.c:1450
-msgid "could not parse HEAD commit"
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
msgstr ""
-#: builtin/commit.c:1487 builtin/merge.c:509
+#: builtin/clone.c:310
#, c-format
-msgid "could not open '%s' for reading"
+msgid "%s exists and is not a directory"
msgstr ""
-#: builtin/commit.c:1494
+#: builtin/clone.c:324
#, c-format
-msgid "Corrupt MERGE_HEAD file (%s)"
+msgid "failed to stat %s\n"
msgstr ""
-#: builtin/commit.c:1501
-msgid "could not read MERGE_MODE"
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
msgstr ""
-#: builtin/commit.c:1520
+#: builtin/clone.c:346
#, c-format
-msgid "could not read commit message: %s"
+msgid "failed to create link '%s'"
msgstr ""
-#: builtin/commit.c:1534
+#: builtin/clone.c:350
#, c-format
-msgid "Aborting commit; you did not edit the message.\n"
+msgid "failed to copy file to '%s'"
msgstr ""
-#: builtin/commit.c:1539
+#: builtin/clone.c:373
#, c-format
-msgid "Aborting commit due to empty commit message.\n"
+msgid "done.\n"
msgstr ""
-#: builtin/commit.c:1554 builtin/merge.c:936 builtin/merge.c:961
-msgid "failed to write commit object"
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
msgstr ""
-#: builtin/commit.c:1575
-msgid "cannot lock HEAD ref"
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr ""
-#: builtin/commit.c:1579
-msgid "cannot update HEAD ref"
+#: builtin/clone.c:639
+msgid "Too many arguments."
msgstr ""
-#: builtin/commit.c:1590
-msgid ""
-"Repository has been updated, but unable to write\n"
-"new_index file. Check that disk is not full or quota is\n"
-"not exceeded, and then \"git reset HEAD\" to recover."
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
msgstr ""
-#: builtin/describe.c:234
+#: builtin/clone.c:654
#, c-format
-msgid "annotated tag %s not available"
+msgid "--bare and --origin %s options are incompatible."
msgstr ""
-#: builtin/describe.c:238
+#: builtin/clone.c:668
#, c-format
-msgid "annotated tag %s has no embedded name"
+msgid "repository '%s' does not exist"
msgstr ""
-#: builtin/describe.c:240
-#, c-format
-msgid "tag '%s' is really '%s' here"
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
msgstr ""
-#: builtin/describe.c:267
+#: builtin/clone.c:683
#, c-format
-msgid "Not a valid object name %s"
+msgid "destination path '%s' already exists and is not an empty directory."
msgstr ""
-#: builtin/describe.c:270
+#: builtin/clone.c:693
#, c-format
-msgid "%s is not a valid '%s' object"
+msgid "working tree '%s' already exists."
msgstr ""
-#: builtin/describe.c:287
+#: builtin/clone.c:706 builtin/clone.c:720
#, c-format
-msgid "no tag exactly matches '%s'"
+msgid "could not create leading directories of '%s'"
msgstr ""
-#: builtin/describe.c:289
+#: builtin/clone.c:709
#, c-format
-msgid "searching to describe %s\n"
+msgid "could not create work tree dir '%s'."
msgstr ""
-#: builtin/describe.c:329
+#: builtin/clone.c:728
#, c-format
-msgid "finished search at %s\n"
+msgid "Cloning into bare repository '%s'...\n"
msgstr ""
-#: builtin/describe.c:353
+#: builtin/clone.c:730
#, c-format
-msgid ""
-"No annotated tags can describe '%s'.\n"
-"However, there were unannotated tags: try --tags."
+msgid "Cloning into '%s'...\n"
msgstr ""
-#: builtin/describe.c:357
+#: builtin/clone.c:786
#, c-format
-msgid ""
-"No tags can describe '%s'.\n"
-"Try --always, or create some tags."
+msgid "Don't know how to clone %s"
msgstr ""
-#: builtin/describe.c:378
+#: builtin/clone.c:835
#, c-format
-msgid "traversed %lu commits\n"
+msgid "Remote branch %s not found in upstream %s"
msgstr ""
-#: builtin/describe.c:381
-#, c-format
-msgid ""
-"more than %i tags found; listed %i most recent\n"
-"gave up search at %s\n"
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
msgstr ""
-#: builtin/describe.c:436
-msgid "--long is incompatible with --abbrev=0"
+#: builtin/column.c:51
+msgid "--command must be the first argument"
msgstr ""
-#: builtin/describe.c:462
-msgid "No names found, cannot describe anything."
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
msgstr ""
-#: builtin/describe.c:482
-msgid "--dirty is incompatible with committishes"
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
msgstr ""
-#: builtin/diff.c:77
-#, c-format
-msgid "'%s': not a regular file or symlink"
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
msgstr ""
-#: builtin/diff.c:220
-#, c-format
-msgid "invalid option: %s"
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
msgstr ""
-#: builtin/diff.c:297
-msgid "Not a git repository"
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
msgstr ""
-#: builtin/diff.c:347
-#, c-format
-msgid "invalid object '%s' given."
+#: builtin/commit.c:301
+msgid "interactive add failed"
msgstr ""
-#: builtin/diff.c:352
-#, c-format
-msgid "more than %d trees given: '%s'"
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
msgstr ""
-#: builtin/diff.c:362
-#, c-format
-msgid "more than two blobs given: '%s'"
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
msgstr ""
-#: builtin/diff.c:370
-#, c-format
-msgid "unhandled object '%s' given."
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
msgstr ""
-#: builtin/fetch.c:200
-msgid "Couldn't find remote ref HEAD"
+#: builtin/commit.c:398
+msgid "cannot read the index"
msgstr ""
-#: builtin/fetch.c:253
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:493 builtin/commit.c:499
#, c-format
-msgid "object %s not found"
+msgid "invalid commit: %s"
msgstr ""
-#: builtin/fetch.c:259
-msgid "[up to date]"
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
msgstr ""
-#: builtin/fetch.c:273
+#: builtin/commit.c:583
#, c-format
-msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgid "Malformed ident string: '%s'"
msgstr ""
-#: builtin/fetch.c:274 builtin/fetch.c:360
-msgid "[rejected]"
+#: builtin/commit.c:621 builtin/commit.c:654 builtin/commit.c:968
+#, c-format
+msgid "could not lookup commit %s"
msgstr ""
-#: builtin/fetch.c:285
-msgid "[tag update]"
+#: builtin/commit.c:633 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
msgstr ""
-#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
-msgid " (unable to update local ref)"
+#: builtin/commit.c:635
+msgid "could not read log from standard input"
msgstr ""
-#: builtin/fetch.c:305
-msgid "[new tag]"
+#: builtin/commit.c:639
+#, c-format
+msgid "could not read log file '%s'"
msgstr ""
-#: builtin/fetch.c:308
-msgid "[new branch]"
+#: builtin/commit.c:645
+msgid "commit has empty message"
msgstr ""
-#: builtin/fetch.c:311
-msgid "[new ref]"
+#: builtin/commit.c:661
+msgid "could not read MERGE_MSG"
msgstr ""
-#: builtin/fetch.c:356
-msgid "unable to update local ref"
+#: builtin/commit.c:665
+msgid "could not read SQUASH_MSG"
msgstr ""
-#: builtin/fetch.c:356
-msgid "forced update"
+#: builtin/commit.c:669
+#, c-format
+msgid "could not read '%s'"
msgstr ""
-#: builtin/fetch.c:362
-msgid "(non-fast-forward)"
+#: builtin/commit.c:721
+msgid "could not write commit template"
msgstr ""
-#: builtin/fetch.c:393 builtin/fetch.c:685
+#: builtin/commit.c:732
#, c-format
-msgid "cannot open %s: %s\n"
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
msgstr ""
-#: builtin/fetch.c:402
+#: builtin/commit.c:737
#, c-format
-msgid "%s did not send all necessary objects\n"
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
msgstr ""
-#: builtin/fetch.c:488
-#, c-format
-msgid "From %.*s\n"
+#: builtin/commit.c:749
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
msgstr ""
-#: builtin/fetch.c:499
-#, c-format
+#: builtin/commit.c:754
msgid ""
-"some local refs could not be updated; try running\n"
-" 'git remote prune %s' to remove any old, conflicting branches"
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
msgstr ""
-#: builtin/fetch.c:549
+#: builtin/commit.c:767
#, c-format
-msgid " (%s will become dangling)\n"
+msgid "%sAuthor: %s"
msgstr ""
-#: builtin/fetch.c:550
+#: builtin/commit.c:774
#, c-format
-msgid " (%s has become dangling)\n"
+msgid "%sCommitter: %s"
msgstr ""
-#: builtin/fetch.c:557
-msgid "[deleted]"
+#: builtin/commit.c:794
+msgid "Cannot read index"
msgstr ""
-#: builtin/fetch.c:558
-msgid "(none)"
+#: builtin/commit.c:831
+msgid "Error building trees"
msgstr ""
-#: builtin/fetch.c:675
+#: builtin/commit.c:846 builtin/tag.c:361
#, c-format
-msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgid "Please supply the message using either -m or -F option.\n"
msgstr ""
-#: builtin/fetch.c:709
+#: builtin/commit.c:943
#, c-format
-msgid "Don't know how to fetch from %s"
+msgid "No existing author found with '%s'"
msgstr ""
-#: builtin/fetch.c:786
+#: builtin/commit.c:958 builtin/commit.c:1158
#, c-format
-msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgid "Invalid untracked files mode '%s'"
msgstr ""
-#: builtin/fetch.c:789
-#, c-format
-msgid "Option \"%s\" is ignored for %s\n"
+#: builtin/commit.c:998
+msgid "Using both --reset-author and --author does not make sense"
msgstr ""
-#: builtin/fetch.c:888
-#, c-format
-msgid "Fetching %s\n"
+#: builtin/commit.c:1009
+msgid "You have nothing to amend."
msgstr ""
-#: builtin/fetch.c:890
-#, c-format
-msgid "Could not fetch %s"
+#: builtin/commit.c:1012
+msgid "You are in the middle of a merge -- cannot amend."
msgstr ""
-#: builtin/fetch.c:907
-msgid ""
-"No remote repository specified. Please, specify either a URL or a\n"
-"remote name from which new revisions should be fetched."
+#: builtin/commit.c:1014
+msgid "You are in the middle of a cherry-pick -- cannot amend."
msgstr ""
-#: builtin/fetch.c:927
-msgid "You need to specify a tag name."
+#: builtin/commit.c:1017
+msgid "Options --squash and --fixup cannot be used together"
msgstr ""
-#: builtin/fetch.c:979
-msgid "fetch --all does not take a repository argument"
+#: builtin/commit.c:1027
+msgid "Only one of -c/-C/-F/--fixup can be used."
msgstr ""
-#: builtin/fetch.c:981
-msgid "fetch --all does not make sense with refspecs"
+#: builtin/commit.c:1029
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
msgstr ""
-#: builtin/fetch.c:992
-#, c-format
-msgid "No such remote or remote group: %s"
+#: builtin/commit.c:1037
+msgid "--reset-author can be used only with -C, -c or --amend."
msgstr ""
-#: builtin/fetch.c:1000
-msgid "Fetching a group and specifying refspecs does not make sense"
+#: builtin/commit.c:1054
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
msgstr ""
-#: builtin/gc.c:63
-#, c-format
-msgid "Invalid %s: '%s'"
+#: builtin/commit.c:1056
+msgid "No paths with --include/--only does not make sense."
msgstr ""
-#: builtin/gc.c:90
-#, c-format
-msgid "insanely long object directory %.*s"
+#: builtin/commit.c:1058
+msgid "Clever... amending the last one with dirty index."
msgstr ""
-#: builtin/gc.c:221
-#, c-format
-msgid "Auto packing the repository for optimum performance.\n"
+#: builtin/commit.c:1060
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
msgstr ""
-#: builtin/gc.c:224
+#: builtin/commit.c:1070 builtin/tag.c:577
#, c-format
-msgid ""
-"Auto packing the repository for optimum performance. You may also\n"
-"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgid "Invalid cleanup mode %s"
msgstr ""
-#: builtin/gc.c:251
-msgid ""
-"There are too many unreachable loose objects; run 'git prune' to remove them."
+#: builtin/commit.c:1075
+msgid "Paths with -a does not make sense."
msgstr ""
-#: builtin/grep.c:216
-#, c-format
-msgid "grep: failed to create thread: %s"
+#: builtin/commit.c:1258
+msgid "couldn't look up newly created commit"
msgstr ""
-#: builtin/grep.c:402
-#, c-format
-msgid "Failed to chdir: %s"
+#: builtin/commit.c:1260
+msgid "could not parse newly created commit"
msgstr ""
-#: builtin/grep.c:478 builtin/grep.c:512
-#, c-format
-msgid "unable to read tree (%s)"
+#: builtin/commit.c:1301
+msgid "detached HEAD"
msgstr ""
-#: builtin/grep.c:526
-#, c-format
-msgid "unable to grep from object of type %s"
+#: builtin/commit.c:1303
+msgid " (root-commit)"
msgstr ""
-#: builtin/grep.c:584
+#: builtin/commit.c:1447
+msgid "could not parse HEAD commit"
+msgstr ""
+
+#: builtin/commit.c:1485 builtin/merge.c:509
#, c-format
-msgid "switch `%c' expects a numerical value"
+msgid "could not open '%s' for reading"
msgstr ""
-#: builtin/grep.c:601
+#: builtin/commit.c:1492
#, c-format
-msgid "cannot open '%s'"
+msgid "Corrupt MERGE_HEAD file (%s)"
msgstr ""
-#: builtin/grep.c:888
-msgid "no pattern given."
+#: builtin/commit.c:1499
+msgid "could not read MERGE_MODE"
msgstr ""
-#: builtin/grep.c:902
+#: builtin/commit.c:1518
#, c-format
-msgid "bad object %s"
+msgid "could not read commit message: %s"
msgstr ""
-#: builtin/grep.c:943
-msgid "--open-files-in-pager only works on the worktree"
+#: builtin/commit.c:1532
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
msgstr ""
-#: builtin/grep.c:966
-msgid "--cached or --untracked cannot be used with --no-index."
+#: builtin/commit.c:1537
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
msgstr ""
-#: builtin/grep.c:971
-msgid "--no-index or --untracked cannot be used with revs."
+#: builtin/commit.c:1552 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
msgstr ""
-#: builtin/grep.c:974
-msgid "--[no-]exclude-standard cannot be used for tracked contents."
+#: builtin/commit.c:1573
+msgid "cannot lock HEAD ref"
msgstr ""
-#: builtin/grep.c:982
-msgid "both --cached and trees are given."
+#: builtin/commit.c:1577
+msgid "cannot update HEAD ref"
msgstr ""
-#: builtin/init-db.c:35
-#, c-format
-msgid "Could not make %s writable by group"
+#: builtin/commit.c:1588
+msgid ""
+"Repository has been updated, but unable to write\n"
+"new_index file. Check that disk is not full or quota is\n"
+"not exceeded, and then \"git reset HEAD\" to recover."
msgstr ""
-#: builtin/init-db.c:62
+#: builtin/describe.c:234
#, c-format
-msgid "insanely long template name %s"
+msgid "annotated tag %s not available"
msgstr ""
-#: builtin/init-db.c:67
+#: builtin/describe.c:238
#, c-format
-msgid "cannot stat '%s'"
+msgid "annotated tag %s has no embedded name"
msgstr ""
-#: builtin/init-db.c:73
-#, c-format
-msgid "cannot stat template '%s'"
-msgstr ""
-
-#: builtin/init-db.c:80
+#: builtin/describe.c:240
#, c-format
-msgid "cannot opendir '%s'"
+msgid "tag '%s' is really '%s' here"
msgstr ""
-#: builtin/init-db.c:97
+#: builtin/describe.c:267
#, c-format
-msgid "cannot readlink '%s'"
+msgid "Not a valid object name %s"
msgstr ""
-#: builtin/init-db.c:99
+#: builtin/describe.c:270
#, c-format
-msgid "insanely long symlink %s"
+msgid "%s is not a valid '%s' object"
msgstr ""
-#: builtin/init-db.c:102
+#: builtin/describe.c:287
#, c-format
-msgid "cannot symlink '%s' '%s'"
+msgid "no tag exactly matches '%s'"
msgstr ""
-#: builtin/init-db.c:106
+#: builtin/describe.c:289
#, c-format
-msgid "cannot copy '%s' to '%s'"
+msgid "searching to describe %s\n"
msgstr ""
-#: builtin/init-db.c:110
+#: builtin/describe.c:329
#, c-format
-msgid "ignoring template %s"
+msgid "finished search at %s\n"
msgstr ""
-#: builtin/init-db.c:133
+#: builtin/describe.c:353
#, c-format
-msgid "insanely long template path %s"
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
msgstr ""
-#: builtin/init-db.c:141
+#: builtin/describe.c:357
#, c-format
-msgid "templates not found %s"
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
msgstr ""
-#: builtin/init-db.c:154
+#: builtin/describe.c:378
#, c-format
-msgid "not copying templates of a wrong format version %d from '%s'"
+msgid "traversed %lu commits\n"
msgstr ""
-#: builtin/init-db.c:192
+#: builtin/describe.c:381
#, c-format
-msgid "insane git directory %s"
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
msgstr ""
-#: builtin/init-db.c:322 builtin/init-db.c:325
-#, c-format
-msgid "%s already exists"
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
msgstr ""
-#: builtin/init-db.c:354
-#, c-format
-msgid "unable to handle file type %d"
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
msgstr ""
-#: builtin/init-db.c:357
-#, c-format
-msgid "unable to move %s to %s"
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
msgstr ""
-#: builtin/init-db.c:362
+#: builtin/diff.c:77
#, c-format
-msgid "Could not create git link %s"
+msgid "'%s': not a regular file or symlink"
msgstr ""
-#.
-#. * TRANSLATORS: The first '%s' is either "Reinitialized
-#. * existing" or "Initialized empty", the second " shared" or
-#. * "", and the last '%s%s' is the verbatim directory name.
-#.
-#: builtin/init-db.c:419
+#: builtin/diff.c:220
#, c-format
-msgid "%s%s Git repository in %s%s\n"
-msgstr ""
-
-#: builtin/init-db.c:420
-msgid "Reinitialized existing"
-msgstr ""
-
-#: builtin/init-db.c:420
-msgid "Initialized empty"
-msgstr ""
-
-#: builtin/init-db.c:421
-msgid " shared"
+msgid "invalid option: %s"
msgstr ""
-#: builtin/init-db.c:440
-msgid "cannot tell cwd"
+#: builtin/diff.c:297
+msgid "Not a git repository"
msgstr ""
-#: builtin/init-db.c:521 builtin/init-db.c:528
+#: builtin/diff.c:347
#, c-format
-msgid "cannot mkdir %s"
+msgid "invalid object '%s' given."
msgstr ""
-#: builtin/init-db.c:532
+#: builtin/diff.c:352
#, c-format
-msgid "cannot chdir to %s"
+msgid "more than %d trees given: '%s'"
msgstr ""
-#: builtin/init-db.c:554
+#: builtin/diff.c:362
#, c-format
-msgid ""
-"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
-"dir=<directory>)"
+msgid "more than two blobs given: '%s'"
msgstr ""
-#: builtin/init-db.c:578
-msgid "Cannot access current working directory"
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
msgstr ""
-#: builtin/init-db.c:585
-#, c-format
-msgid "Cannot access work tree '%s'"
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
msgstr ""
-#: builtin/log.c:187
+#: builtin/fetch.c:253
#, c-format
-msgid "Final output: %d %s\n"
+msgid "object %s not found"
msgstr ""
-#: builtin/log.c:395 builtin/log.c:483
-#, c-format
-msgid "Could not read object %s"
+#: builtin/fetch.c:259
+msgid "[up to date]"
msgstr ""
-#: builtin/log.c:507
+#: builtin/fetch.c:273
#, c-format
-msgid "Unknown type: %d"
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
msgstr ""
-#: builtin/log.c:596
-msgid "format.headers without value"
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
msgstr ""
-#: builtin/log.c:669
-msgid "name of output directory is too long"
+#: builtin/fetch.c:285
+msgid "[tag update]"
msgstr ""
-#: builtin/log.c:680
-#, c-format
-msgid "Cannot open patch file %s"
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
msgstr ""
-#: builtin/log.c:694
-msgid "Need exactly one range."
+#: builtin/fetch.c:305
+msgid "[new tag]"
msgstr ""
-#: builtin/log.c:702
-msgid "Not a range."
+#: builtin/fetch.c:308
+msgid "[new branch]"
msgstr ""
-#: builtin/log.c:739
-msgid "Could not extract email from committer identity."
+#: builtin/fetch.c:311
+msgid "[new ref]"
msgstr ""
-#: builtin/log.c:785
-msgid "Cover letter needs email format"
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
msgstr ""
-#: builtin/log.c:879
-#, c-format
-msgid "insane in-reply-to: %s"
+#: builtin/fetch.c:356
+msgid "forced update"
msgstr ""
-#: builtin/log.c:952
-msgid "Two output directories?"
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
msgstr ""
-#: builtin/log.c:1173
+#: builtin/fetch.c:393 builtin/fetch.c:685
#, c-format
-msgid "bogus committer info %s"
+msgid "cannot open %s: %s\n"
msgstr ""
-#: builtin/log.c:1218
-msgid "-n and -k are mutually exclusive."
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
msgstr ""
-#: builtin/log.c:1220
-msgid "--subject-prefix and -k are mutually exclusive."
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
msgstr ""
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/fetch.c:499
#, c-format
-msgid "unrecognized argument: %s"
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
msgstr ""
-#: builtin/log.c:1228
-msgid "--name-only does not make sense"
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
msgstr ""
-#: builtin/log.c:1230
-msgid "--name-status does not make sense"
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
msgstr ""
-#: builtin/log.c:1232
-msgid "--check does not make sense"
+#: builtin/fetch.c:557
+msgid "[deleted]"
msgstr ""
-#: builtin/log.c:1255
-msgid "standard output, or directory, which one?"
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
msgstr ""
-#: builtin/log.c:1257
+#: builtin/fetch.c:675
#, c-format
-msgid "Could not create directory '%s'"
-msgstr ""
-
-#: builtin/log.c:1410
-msgid "Failed to create output files"
+msgid "Refusing to fetch into current branch %s of non-bare repository"
msgstr ""
-#: builtin/log.c:1514
+#: builtin/fetch.c:709
#, c-format
-msgid ""
-"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgid "Don't know how to fetch from %s"
msgstr ""
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/fetch.c:786
#, c-format
-msgid "Unknown commit %s"
-msgstr ""
-
-#: builtin/merge.c:90
-msgid "switch `m' requires a value"
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
msgstr ""
-#: builtin/merge.c:127
+#: builtin/fetch.c:789
#, c-format
-msgid "Could not find merge strategy '%s'.\n"
+msgid "Option \"%s\" is ignored for %s\n"
msgstr ""
-#: builtin/merge.c:128
+#: builtin/fetch.c:888
#, c-format
-msgid "Available strategies are:"
+msgid "Fetching %s\n"
msgstr ""
-#: builtin/merge.c:133
+#: builtin/fetch.c:890 builtin/remote.c:100
#, c-format
-msgid "Available custom strategies are:"
-msgstr ""
-
-#: builtin/merge.c:240
-msgid "could not run stash."
+msgid "Could not fetch %s"
msgstr ""
-#: builtin/merge.c:245
-msgid "stash failed"
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
msgstr ""
-#: builtin/merge.c:250
-#, c-format
-msgid "not a valid object: %s"
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
msgstr ""
-#: builtin/merge.c:269 builtin/merge.c:286
-msgid "read-tree failed"
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
msgstr ""
-#: builtin/merge.c:316
-msgid " (nothing to squash)"
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
msgstr ""
-#: builtin/merge.c:329
+#: builtin/fetch.c:992
#, c-format
-msgid "Squash commit -- not updating HEAD\n"
+msgid "No such remote or remote group: %s"
msgstr ""
-#: builtin/merge.c:361
-msgid "Writing SQUASH_MSG"
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
msgstr ""
-#: builtin/merge.c:363
-msgid "Finishing SQUASH_MSG"
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
msgstr ""
-#: builtin/merge.c:386
+#: builtin/gc.c:90
#, c-format
-msgid "No merge message -- not updating HEAD\n"
+msgid "insanely long object directory %.*s"
msgstr ""
-#: builtin/merge.c:437
+#: builtin/gc.c:221
#, c-format
-msgid "'%s' does not point to a commit"
+msgid "Auto packing the repository for optimum performance.\n"
msgstr ""
-#: builtin/merge.c:536
+#: builtin/gc.c:224
#, c-format
-msgid "Bad branch.%s.mergeoptions string: %s"
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
msgstr ""
-#: builtin/merge.c:629
-msgid "git write-tree failed to write a tree"
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
msgstr ""
-#: builtin/merge.c:679
-msgid "failed to read the cache"
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
msgstr ""
-#: builtin/merge.c:697
-msgid "Unable to write index."
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
msgstr ""
-#: builtin/merge.c:710
-msgid "Not handling anything other than two heads merge."
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
msgstr ""
-#: builtin/merge.c:724
+#: builtin/grep.c:526
#, c-format
-msgid "Unknown option for merge-recursive: -X%s"
+msgid "unable to grep from object of type %s"
msgstr ""
-#: builtin/merge.c:738
+#: builtin/grep.c:584
#, c-format
-msgid "unable to write %s"
+msgid "switch `%c' expects a numerical value"
msgstr ""
-#: builtin/merge.c:877
+#: builtin/grep.c:601
#, c-format
-msgid "Could not read from '%s'"
+msgid "cannot open '%s'"
msgstr ""
-#: builtin/merge.c:886
+#: builtin/grep.c:888
+msgid "no pattern given."
+msgstr ""
+
+#: builtin/grep.c:902
#, c-format
-msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgid "bad object %s"
msgstr ""
-#: builtin/merge.c:892
+#: builtin/grep.c:943
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:966
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr ""
+
+#: builtin/grep.c:971
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr ""
+
+#: builtin/grep.c:974
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:982
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr ""
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr ""
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr ""
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr ""
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr ""
+
+#: builtin/help.c:211
+#, c-format
msgid ""
-"Please enter a commit message to explain why this merge is necessary,\n"
-"especially if it merges an updated upstream into a topic branch.\n"
-"\n"
-"Lines starting with '#' will be ignored, and an empty message aborts\n"
-"the commit.\n"
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
msgstr ""
-#: builtin/merge.c:916
-msgid "Empty commit message."
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
msgstr ""
-#: builtin/merge.c:928
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr ""
+
+#: builtin/help.c:355
#, c-format
-msgid "Wonderful.\n"
+msgid "'%s': unknown man viewer."
msgstr ""
-#: builtin/merge.c:993
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:391
#, c-format
-msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgid "'%s': not a documentation directory."
msgstr ""
-#: builtin/merge.c:1009
+#: builtin/help.c:432 builtin/help.c:439
#, c-format
-msgid "'%s' is not a commit"
+msgid "usage: %s%s"
msgstr ""
-#: builtin/merge.c:1050
-msgid "No current branch."
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
msgstr ""
-#: builtin/merge.c:1052
-msgid "No remote for the current branch."
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
msgstr ""
-#: builtin/merge.c:1054
-msgid "No default upstream defined for the current branch."
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
msgstr ""
-#: builtin/merge.c:1059
+#: builtin/index-pack.c:226
#, c-format
-msgid "No remote tracking branch for %s from %s"
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
msgstr ""
-#: builtin/merge.c:1146 builtin/merge.c:1303
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr ""
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr ""
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr ""
+
+#: builtin/index-pack.c:272
#, c-format
-msgid "%s - not something we can merge"
+msgid "unable to create '%s'"
msgstr ""
-#: builtin/merge.c:1214
-msgid "There is no merge to abort (MERGE_HEAD missing)."
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
msgstr ""
-#: builtin/merge.c:1230 git-pull.sh:31
-msgid ""
-"You have not concluded your merge (MERGE_HEAD exists).\n"
-"Please, commit your changes before you can merge."
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
msgstr ""
-#: builtin/merge.c:1233 git-pull.sh:34
-msgid "You have not concluded your merge (MERGE_HEAD exists)."
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
msgstr ""
-#: builtin/merge.c:1237
-msgid ""
-"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
-"Please, commit your changes before you can merge."
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
msgstr ""
-#: builtin/merge.c:1240
-msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
msgstr ""
-#: builtin/merge.c:1249
-msgid "You cannot combine --squash with --no-ff."
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
msgstr ""
-#: builtin/merge.c:1254
-msgid "You cannot combine --no-ff with --ff-only."
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
msgstr ""
-#: builtin/merge.c:1261
-msgid "No commit specified and merge.defaultToUpstream not set."
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
msgstr ""
-#: builtin/merge.c:1293
-msgid "Can merge only exactly one commit into empty head"
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
msgstr ""
-#: builtin/merge.c:1296
-msgid "Squash commit into empty head not supported yet"
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
msgstr ""
-#: builtin/merge.c:1298
-msgid "Non-fast-forward commit does not make sense into an empty head"
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
msgstr ""
-#: builtin/merge.c:1413
+#: builtin/index-pack.c:598
#, c-format
-msgid "Updating %s..%s\n"
+msgid "invalid blob object %s"
msgstr ""
-#: builtin/merge.c:1451
+#: builtin/index-pack.c:610
#, c-format
-msgid "Trying really trivial in-index merge...\n"
+msgid "invalid %s"
msgstr ""
-#: builtin/merge.c:1458
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr ""
+
+#: builtin/index-pack.c:614
#, c-format
-msgid "Nope.\n"
+msgid "Not all child objects of %s are reachable"
msgstr ""
-#: builtin/merge.c:1490
-msgid "Not possible to fast-forward, aborting."
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
msgstr ""
-#: builtin/merge.c:1513 builtin/merge.c:1592
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr ""
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr ""
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr ""
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr ""
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr ""
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr ""
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr ""
+
+#: builtin/index-pack.c:973
#, c-format
-msgid "Rewinding the tree to pristine...\n"
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
msgstr ""
-#: builtin/merge.c:1517
+#: builtin/index-pack.c:1077
#, c-format
-msgid "Trying merge strategy %s...\n"
+msgid "local object %s is corrupt"
msgstr ""
-#: builtin/merge.c:1583
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr ""
+
+#: builtin/index-pack.c:1114
#, c-format
-msgid "No merge strategy handled the merge.\n"
+msgid "cannot write keep file '%s'"
msgstr ""
-#: builtin/merge.c:1585
+#: builtin/index-pack.c:1122
#, c-format
-msgid "Merge with strategy %s failed.\n"
+msgid "cannot close written keep file '%s'"
msgstr ""
-#: builtin/merge.c:1594
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr ""
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr ""
+
+#: builtin/index-pack.c:1247
#, c-format
-msgid "Using the %s to prepare resolving by hand.\n"
+msgid "Cannot open existing pack file '%s'"
msgstr ""
-#: builtin/merge.c:1606
+#: builtin/index-pack.c:1249
#, c-format
-msgid "Automatic merge went well; stopped before committing as requested\n"
+msgid "Cannot open existing pack idx file for '%s'"
msgstr ""
-#: builtin/mv.c:108
+#: builtin/index-pack.c:1296
#, c-format
-msgid "Checking rename of '%s' to '%s'\n"
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
msgstr ""
-#: builtin/mv.c:112
-msgid "bad source"
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
msgstr ""
-#: builtin/mv.c:115
-msgid "can not move directory into itself"
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr ""
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr ""
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr ""
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr ""
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr ""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr ""
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr ""
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr ""
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr ""
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr ""
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr ""
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr ""
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr ""
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr ""
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr ""
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr ""
+
+#: builtin/log.c:675
+msgid "name of output directory is too long"
+msgstr ""
+
+#: builtin/log.c:686
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr ""
+
+#: builtin/log.c:700
+msgid "Need exactly one range."
+msgstr ""
+
+#: builtin/log.c:708
+msgid "Not a range."
+msgstr ""
+
+#: builtin/log.c:745
+msgid "Could not extract email from committer identity."
+msgstr ""
+
+#: builtin/log.c:791
+msgid "Cover letter needs email format"
+msgstr ""
+
+#: builtin/log.c:885
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:958
+msgid "Two output directories?"
+msgstr ""
+
+#: builtin/log.c:1179
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1224
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1226
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1234
+msgid "--name-only does not make sense"
+msgstr ""
+
+#: builtin/log.c:1236
+msgid "--name-status does not make sense"
+msgstr ""
+
+#: builtin/log.c:1238
+msgid "--check does not make sense"
+msgstr ""
+
+#: builtin/log.c:1261
+msgid "standard output, or directory, which one?"
+msgstr ""
+
+#: builtin/log.c:1263
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr ""
+
+#: builtin/log.c:1416
+msgid "Failed to create output files"
+msgstr ""
+
+#: builtin/log.c:1520
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
+#, c-format
+msgid "Unknown commit %s"
+msgstr ""
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr ""
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr ""
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr ""
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr ""
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr ""
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr ""
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr ""
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr ""
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr ""
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr ""
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr ""
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr ""
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr ""
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr ""
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr ""
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr ""
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr ""
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr ""
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr ""
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr ""
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr ""
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr ""
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr ""
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr ""
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr ""
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr ""
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr ""
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr ""
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr ""
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr ""
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr ""
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr ""
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr ""
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr ""
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr ""
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr ""
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr ""
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr ""
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr ""
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr ""
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr ""
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr ""
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr ""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr ""
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr ""
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr ""
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr ""
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr ""
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr ""
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr ""
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr ""
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr ""
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr ""
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr ""
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr ""
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr ""
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2315
+#, c-format
+msgid "unsupported index version %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2319
+#, c-format
+msgid "bad index version '%s'"
+msgstr ""
+
+#: builtin/pack-objects.c:2342
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr ""
+
+#: builtin/pack-objects.c:2346
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr ""
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr ""
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr ""
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr ""
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr ""
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr ""
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
msgstr ""
-#: builtin/mv.c:118
-msgid "cannot move directory over file"
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
msgstr ""
-#: builtin/mv.c:128
+#: builtin/remote.c:98
#, c-format
-msgid "Huh? %.*s is in index?"
+msgid "Updating %s"
msgstr ""
-#: builtin/mv.c:140
-msgid "source directory is empty"
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
msgstr ""
-#: builtin/mv.c:171
-msgid "not under version control"
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
msgstr ""
-#: builtin/mv.c:173
-msgid "destination exists"
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
msgstr ""
-#: builtin/mv.c:181
-#, c-format
-msgid "overwriting '%s'"
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
msgstr ""
-#: builtin/mv.c:184
-msgid "Cannot overwrite"
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
msgstr ""
-#: builtin/mv.c:187
-msgid "multiple sources for the same target"
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
msgstr ""
-#: builtin/mv.c:202
+#: builtin/remote.c:243
#, c-format
-msgid "%s, source=%s, destination=%s"
+msgid "Could not setup master '%s'"
msgstr ""
-#: builtin/mv.c:212
+#: builtin/remote.c:299
#, c-format
-msgid "Renaming %s to %s\n"
+msgid "more than one %s"
msgstr ""
-#: builtin/mv.c:215
+#: builtin/remote.c:339
#, c-format
-msgid "renaming '%s' failed"
+msgid "Could not get fetch map for refspec %s"
msgstr ""
-#: builtin/notes.c:139
-#, c-format
-msgid "unable to start 'show' for object '%s'"
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
msgstr ""
-#: builtin/notes.c:145
-msgid "can't fdopen 'show' output fd"
+#: builtin/remote.c:452
+msgid "(delete)"
msgstr ""
-#: builtin/notes.c:155
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
#, c-format
-msgid "failed to close pipe to 'show' for object '%s'"
+msgid "Could not append '%s' to '%s'"
msgstr ""
-#: builtin/notes.c:158
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
#, c-format
-msgid "failed to finish 'show' for object '%s'"
+msgid "No such remote: %s"
msgstr ""
-#: builtin/notes.c:175 builtin/tag.c:343
+#: builtin/remote.c:656
#, c-format
-msgid "could not create file '%s'"
+msgid "Could not rename config section '%s' to '%s'"
msgstr ""
-#: builtin/notes.c:189
-msgid "Please supply the note contents using either -m or -F option"
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
msgstr ""
-#: builtin/notes.c:210 builtin/notes.c:973
+#: builtin/remote.c:677
#, c-format
-msgid "Removing note for object %s\n"
+msgid ""
+"Not updating non-default fetch respec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
msgstr ""
-#: builtin/notes.c:215
-msgid "unable to write note object"
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
msgstr ""
-#: builtin/notes.c:217
+#: builtin/remote.c:694
#, c-format
-msgid "The note contents has been left in %s"
+msgid "Could not set '%s'"
msgstr ""
-#: builtin/notes.c:251 builtin/tag.c:521
+#: builtin/remote.c:716
#, c-format
-msgid "cannot read '%s'"
+msgid "deleting '%s' failed"
msgstr ""
-#: builtin/notes.c:253 builtin/tag.c:524
+#: builtin/remote.c:750
#, c-format
-msgid "could not open or read '%s'"
+msgid "creating '%s' failed"
msgstr ""
-#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
-#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
-#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
-#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:537
+#: builtin/remote.c:764
#, c-format
-msgid "Failed to resolve '%s' as a valid ref."
+msgid "Could not remove branch %s"
msgstr ""
-#: builtin/notes.c:275
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:943
#, c-format
-msgid "Failed to read object '%s'."
+msgid " new (next fetch will store in remotes/%s)"
msgstr ""
-#: builtin/notes.c:299
-msgid "Cannot commit uninitialized/unreferenced notes tree"
+#: builtin/remote.c:946
+msgid " tracked"
msgstr ""
-#: builtin/notes.c:340
-#, c-format
-msgid "Bad notes.rewriteMode value: '%s'"
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
msgstr ""
-#: builtin/notes.c:350
-#, c-format
-msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+#: builtin/remote.c:950
+msgid " ???"
msgstr ""
-#. TRANSLATORS: The first %s is the name of the
-#. environment variable, the second %s is its value
-#: builtin/notes.c:377
+#: builtin/remote.c:991
#, c-format
-msgid "Bad %s value: '%s'"
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
msgstr ""
-#: builtin/notes.c:441
+#: builtin/remote.c:998
#, c-format
-msgid "Malformed input line: '%s'."
+msgid "rebases onto remote %s"
msgstr ""
-#: builtin/notes.c:456
+#: builtin/remote.c:1001
#, c-format
-msgid "Failed to copy notes from '%s' to '%s'"
+msgid " merges with remote %s"
msgstr ""
-#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
-#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
-#: builtin/notes.c:1033
-msgid "too many parameters"
+#: builtin/remote.c:1002
+msgid " and with remote"
msgstr ""
-#: builtin/notes.c:513 builtin/notes.c:772
+#: builtin/remote.c:1004
#, c-format
-msgid "No note found for object %s."
+msgid "merges with remote %s"
msgstr ""
-#: builtin/notes.c:580
-#, c-format
-msgid ""
-"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
-"existing notes"
+#: builtin/remote.c:1005
+msgid " and with remote"
msgstr ""
-#: builtin/notes.c:585 builtin/notes.c:662
-#, c-format
-msgid "Overwriting existing notes for object %s\n"
+#: builtin/remote.c:1051
+msgid "create"
msgstr ""
-#: builtin/notes.c:635
-msgid "too few parameters"
+#: builtin/remote.c:1054
+msgid "delete"
msgstr ""
-#: builtin/notes.c:656
-#, c-format
-msgid ""
-"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
-"existing notes"
+#: builtin/remote.c:1058
+msgid "up to date"
msgstr ""
-#: builtin/notes.c:668
-#, c-format
-msgid "Missing notes on source object %s. Cannot copy."
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
msgstr ""
-#: builtin/notes.c:717
-#, c-format
-msgid ""
-"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
-"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+#: builtin/remote.c:1064
+msgid "local out of date"
msgstr ""
-#: builtin/notes.c:971
+#: builtin/remote.c:1071
#, c-format
-msgid "Object %s has no note\n"
+msgid " %-*s forces to %-*s (%s)"
msgstr ""
-#: builtin/notes.c:1103
+#: builtin/remote.c:1074
#, c-format
-msgid "Unknown subcommand: %s"
+msgid " %-*s pushes to %-*s (%s)"
msgstr ""
-#: builtin/pack-objects.c:2315
+#: builtin/remote.c:1078
#, c-format
-msgid "unsupported index version %s"
+msgid " %-*s forces to %s"
msgstr ""
-#: builtin/pack-objects.c:2319
+#: builtin/remote.c:1081
#, c-format
-msgid "bad index version '%s'"
+msgid " %-*s pushes to %s"
msgstr ""
-#: builtin/pack-objects.c:2342
+#: builtin/remote.c:1118
#, c-format
-msgid "option %s does not accept negative form"
+msgid "* remote %s"
msgstr ""
-#: builtin/pack-objects.c:2346
+#: builtin/remote.c:1119
#, c-format
-msgid "unable to parse value '%s' for option %s"
+msgid " Fetch URL: %s"
msgstr ""
-#: builtin/push.c:45
-msgid "tag shorthand without <tag>"
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
msgstr ""
-#: builtin/push.c:64
-msgid "--delete only accepts plain target ref names"
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
msgstr ""
-#: builtin/push.c:84
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
#, c-format
-msgid ""
-"You are not currently on a branch.\n"
-"To push the history leading to the current (detached HEAD)\n"
-"state now, use\n"
-"\n"
-" git push %s HEAD:<name-of-remote-branch>\n"
+msgid " HEAD branch: %s"
msgstr ""
-#: builtin/push.c:91
+#: builtin/remote.c:1139
#, c-format
msgid ""
-"The current branch %s has no upstream branch.\n"
-"To push the current branch and set the remote as upstream, use\n"
-"\n"
-" git push --set-upstream %s %s\n"
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
msgstr ""
-#: builtin/push.c:99
+#: builtin/remote.c:1151
#, c-format
-msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
msgstr ""
-#: builtin/push.c:102
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr ""
+
+#: builtin/remote.c:1178
#, c-format
-msgid ""
-"You are pushing to remote '%s', which is not the upstream of\n"
-"your current branch '%s', without telling me what to push\n"
-"to update which remote branch."
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
msgstr ""
-#: builtin/push.c:131
-msgid ""
-"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
msgstr ""
-#: builtin/push.c:138
-msgid ""
-"Updates were rejected because the tip of your current branch is behind\n"
-"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
-"before pushing again.\n"
-"See the 'Note about fast-forwards' in 'git push --help' for details."
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
msgstr ""
-#: builtin/push.c:144
-msgid ""
-"Updates were rejected because a pushed branch tip is behind its remote\n"
-"counterpart. If you did not intend to push that branch, you may want to\n"
-"specify branches to push or set the 'push.default' configuration\n"
-"variable to 'current' or 'upstream' to push only the current branch."
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
msgstr ""
-#: builtin/push.c:150
-msgid ""
-"Updates were rejected because a pushed branch tip is behind its remote\n"
-"counterpart. Check out this branch and merge the remote changes\n"
-"(e.g. 'git pull') before pushing again.\n"
-"See the 'Note about fast-forwards' in 'git push --help' for details."
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
msgstr ""
-#: builtin/push.c:190
+#: builtin/remote.c:1274
#, c-format
-msgid "Pushing to %s\n"
+msgid " %s will become dangling!"
msgstr ""
-#: builtin/push.c:194
+#: builtin/remote.c:1275
#, c-format
-msgid "failed to push some refs to '%s'"
+msgid " %s has become dangling!"
msgstr ""
-#: builtin/push.c:226
+#: builtin/remote.c:1281
#, c-format
-msgid "bad repository '%s'"
+msgid "Pruning %s"
msgstr ""
-#: builtin/push.c:227
-msgid ""
-"No configured push destination.\n"
-"Either specify the URL from the command-line or configure a remote "
-"repository using\n"
-"\n"
-" git remote add <name> <url>\n"
-"\n"
-"and then push using the remote name\n"
-"\n"
-" git push <name>\n"
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
msgstr ""
-#: builtin/push.c:242
-msgid "--all and --tags are incompatible"
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
msgstr ""
-#: builtin/push.c:243
-msgid "--all can't be combined with refspecs"
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
msgstr ""
-#: builtin/push.c:248
-msgid "--mirror and --tags are incompatible"
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
msgstr ""
-#: builtin/push.c:249
-msgid "--mirror can't be combined with refspecs"
+#: builtin/remote.c:1414
+msgid "no remote specified"
msgstr ""
-#: builtin/push.c:254
-msgid "--all and --mirror are incompatible"
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
msgstr ""
-#: builtin/push.c:334
-msgid "--delete is incompatible with --all, --mirror and --tags"
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
msgstr ""
-#: builtin/push.c:336
-msgid "--delete doesn't make sense without any refs"
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr ""
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
msgstr ""
#: builtin/reset.c:33
msgid "hard"
msgstr ""
+#: builtin/reset.c:33
+msgid "merge"
+msgstr ""
+
#: builtin/reset.c:33
msgid "keep"
msgstr ""
msgid "%s: %s cannot be used with %s"
msgstr ""
-#: builtin/revert.c:127
+#: builtin/revert.c:131
msgid "program error"
msgstr ""
-#: builtin/revert.c:213
+#: builtin/revert.c:221
msgid "revert failed"
msgstr ""
-#: builtin/revert.c:228
+#: builtin/revert.c:236
msgid "cherry-pick failed"
msgstr ""
msgid "Missing author: %s"
msgstr ""
-#: builtin/tag.c:58
+#: builtin/tag.c:60
#, c-format
msgid "malformed object at '%s'"
msgstr ""
-#: builtin/tag.c:205
+#: builtin/tag.c:207
#, c-format
msgid "tag name too long: %.*s..."
msgstr ""
-#: builtin/tag.c:210
+#: builtin/tag.c:212
#, c-format
msgid "tag '%s' not found."
msgstr ""
-#: builtin/tag.c:225
+#: builtin/tag.c:227
#, c-format
msgid "Deleted tag '%s' (was %s)\n"
msgstr ""
-#: builtin/tag.c:237
+#: builtin/tag.c:239
#, c-format
msgid "could not verify the tag '%s'"
msgstr ""
-#: builtin/tag.c:247
+#: builtin/tag.c:249
msgid ""
"\n"
"#\n"
"#\n"
msgstr ""
-#: builtin/tag.c:254
+#: builtin/tag.c:256
msgid ""
"\n"
"#\n"
"#\n"
msgstr ""
-#: builtin/tag.c:294
+#: builtin/tag.c:298
msgid "unable to sign the tag"
msgstr ""
-#: builtin/tag.c:296
+#: builtin/tag.c:300
msgid "unable to write tag file"
msgstr ""
-#: builtin/tag.c:321
+#: builtin/tag.c:325
msgid "bad object type."
msgstr ""
-#: builtin/tag.c:334
+#: builtin/tag.c:338
msgid "tag header too big."
msgstr ""
-#: builtin/tag.c:366
+#: builtin/tag.c:370
msgid "no tag message?"
msgstr ""
-#: builtin/tag.c:372
+#: builtin/tag.c:376
#, c-format
msgid "The tag message has been left in %s\n"
msgstr ""
-#: builtin/tag.c:421
+#: builtin/tag.c:425
msgid "switch 'points-at' requires an object"
msgstr ""
-#: builtin/tag.c:423
+#: builtin/tag.c:427
#, c-format
msgid "malformed object name '%s'"
msgstr ""
-#: builtin/tag.c:502
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr ""
+
+#: builtin/tag.c:523
msgid "-n option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:504
+#: builtin/tag.c:525
msgid "--contains option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:506
+#: builtin/tag.c:527
msgid "--points-at option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:514
+#: builtin/tag.c:535
msgid "only one -F or -m option is allowed."
msgstr ""
-#: builtin/tag.c:534
+#: builtin/tag.c:555
msgid "too many params"
msgstr ""
-#: builtin/tag.c:540
+#: builtin/tag.c:561
#, c-format
msgid "'%s' is not a valid tag name."
msgstr ""
-#: builtin/tag.c:545
+#: builtin/tag.c:566
#, c-format
msgid "tag '%s' already exists"
msgstr ""
-#: builtin/tag.c:563
+#: builtin/tag.c:584
#, c-format
msgid "%s: cannot lock the ref"
msgstr ""
-#: builtin/tag.c:565
+#: builtin/tag.c:586
#, c-format
msgid "%s: cannot update the ref"
msgstr ""
-#: builtin/tag.c:567
+#: builtin/tag.c:588
#, c-format
msgid "Updated tag '%s' (was %s)\n"
msgstr ""
-#: git-am.sh:49
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr ""
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr ""
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr ""
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr ""
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr ""
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr ""
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr ""
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr ""
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr ""
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr ""
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr ""
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr ""
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr ""
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr ""
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr ""
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr ""
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr ""
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr ""
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr ""
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr ""
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr ""
+
+#: git-am.sh:50
msgid "You need to set your committer info first"
msgstr ""
-#: git-am.sh:136
+#: git-am.sh:137
msgid "Repository lacks necessary blobs to fall back on 3-way merge."
msgstr ""
-#: git-am.sh:147
+#: git-am.sh:154
msgid ""
"Did you hand edit your patch?\n"
"It does not apply to blobs recorded in its index."
msgstr ""
-#: git-am.sh:156
+#: git-am.sh:163
msgid "Falling back to patching base and 3-way merge..."
msgstr ""
-#: git-am.sh:268
+#: git-am.sh:275
msgid "Only one StGIT patch series can be applied at once"
msgstr ""
-#: git-am.sh:355
+#: git-am.sh:362
#, sh-format
msgid "Patch format $patch_format is not supported."
msgstr ""
-#: git-am.sh:357
+#: git-am.sh:364
msgid "Patch format detection failed."
msgstr ""
-#: git-am.sh:411
+#: git-am.sh:418
msgid "-d option is no longer supported. Do not use."
msgstr ""
-#: git-am.sh:474
+#: git-am.sh:481
#, sh-format
msgid "previous rebase directory $dotest still exists but mbox given."
msgstr ""
-#: git-am.sh:479
+#: git-am.sh:486
msgid "Please make up your mind. --skip or --abort?"
msgstr ""
-#: git-am.sh:506
+#: git-am.sh:513
msgid "Resolve operation not in progress, we are not resuming."
msgstr ""
-#: git-am.sh:572
+#: git-am.sh:579
#, sh-format
msgid "Dirty index: cannot apply patches (dirty: $files)"
msgstr ""
-#: git-am.sh:748
+#: git-am.sh:755
msgid "cannot be interactive without stdin connected to a terminal."
msgstr ""
#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
#. in your translation. The program will only accept English
#. input at this point.
-#: git-am.sh:759
+#: git-am.sh:766
msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
msgstr ""
-#: git-am.sh:795
+#: git-am.sh:802
#, sh-format
msgid "Applying: $FIRSTLINE"
msgstr ""
-#: git-am.sh:840
+#: git-am.sh:847
msgid "No changes -- Patch already applied."
msgstr ""
-#: git-am.sh:866
+#: git-am.sh:873
msgid "applying to an empty history"
msgstr ""
msgid "cannot strip one component off url '$remoteurl'"
msgstr ""
-#: git-submodule.sh:108
+#: git-submodule.sh:109
#, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
msgstr ""
-#: git-submodule.sh:149
+#: git-submodule.sh:150
#, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
msgstr ""
-#: git-submodule.sh:159
+#: git-submodule.sh:160
#, sh-format
msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
msgstr ""
-#: git-submodule.sh:248
+#: git-submodule.sh:249
#, sh-format
msgid "repo URL: '$repo' must be absolute or begin with ./|../"
msgstr ""
-#: git-submodule.sh:265
+#: git-submodule.sh:266
#, sh-format
-msgid "'$path' already exists in the index"
+msgid "'$sm_path' already exists in the index"
msgstr ""
-#: git-submodule.sh:282
+#: git-submodule.sh:283
#, sh-format
-msgid "'$path' already exists and is not a valid git repo"
+msgid "'$sm_path' already exists and is not a valid git repo"
msgstr ""
-#: git-submodule.sh:296
+#: git-submodule.sh:297
#, sh-format
-msgid "Unable to checkout submodule '$path'"
+msgid "Unable to checkout submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:301
+#: git-submodule.sh:302
#, sh-format
-msgid "Failed to add submodule '$path'"
+msgid "Failed to add submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:306
+#: git-submodule.sh:307
#, sh-format
-msgid "Failed to register submodule '$path'"
+msgid "Failed to register submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:348
+#: git-submodule.sh:349
#, sh-format
-msgid "Entering '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
msgstr ""
-#: git-submodule.sh:360
+#: git-submodule.sh:363
#, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
+msgid "Stopping at '$sm_path'; script returned non-zero status."
msgstr ""
-#: git-submodule.sh:402
+#: git-submodule.sh:405
#, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
msgstr ""
-#: git-submodule.sh:411
+#: git-submodule.sh:414
#, sh-format
-msgid "Failed to register url for submodule path '$path'"
+msgid "Failed to register url for submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:419
+#: git-submodule.sh:422
#, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
+msgid "Failed to register update mode for submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:421
+#: git-submodule.sh:424
#, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
msgstr ""
-#: git-submodule.sh:520
+#: git-submodule.sh:523
#, sh-format
msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
"Maybe you want to use 'update --init'?"
msgstr ""
-#: git-submodule.sh:533
+#: git-submodule.sh:536
#, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
+msgid "Unable to find current revision in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:552
+#: git-submodule.sh:555
#, sh-format
-msgid "Unable to fetch in submodule path '$path'"
+msgid "Unable to fetch in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:566
+#: git-submodule.sh:569
#, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:567
+#: git-submodule.sh:570
#, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
msgstr ""
-#: git-submodule.sh:572
+#: git-submodule.sh:575
#, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:573
+#: git-submodule.sh:576
#, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
msgstr ""
-#: git-submodule.sh:578
+#: git-submodule.sh:581
#, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:579
+#: git-submodule.sh:582
#, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
msgstr ""
-#: git-submodule.sh:601 git-submodule.sh:924
+#: git-submodule.sh:604 git-submodule.sh:927
#, sh-format
-msgid "Failed to recurse into submodule path '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:709
+#: git-submodule.sh:712
msgid "--"
msgstr ""
-#: git-submodule.sh:767
+#: git-submodule.sh:770
#, sh-format
msgid " Warn: $name doesn't contain commit $sha1_src"
msgstr ""
-#: git-submodule.sh:770
+#: git-submodule.sh:773
#, sh-format
msgid " Warn: $name doesn't contain commit $sha1_dst"
msgstr ""
-#: git-submodule.sh:773
+#: git-submodule.sh:776
#, sh-format
msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
msgstr ""
-#: git-submodule.sh:798
+#: git-submodule.sh:801
msgid "blob"
msgstr ""
-#: git-submodule.sh:799
+#: git-submodule.sh:802
msgid "submodule"
msgstr ""
-#: git-submodule.sh:970
+#: git-submodule.sh:973
#, sh-format
msgid "Synchronizing submodule url for '$name'"
msgstr ""
msgstr ""
"Project-Id-Version: Git\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-03-16 20:18+0800\n"
-"PO-Revision-Date: 2012-04-05 20:48+0100\n"
+"POT-Creation-Date: 2012-05-08 16:06+0800\n"
+"PO-Revision-Date: 2012-05-14 21:17+0100\n"
"Last-Translator: Marco Sousa <marcomsousa AT gmail.com>\n"
"Language-Team: Portuguese\n"
-"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: pt\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: advice.c:34
+#: advice.c:40
#, c-format
msgid "hint: %.*s\n"
msgstr "dica: %.*s\n"
#. * Message used both when 'git commit' fails and when
#. * other commands doing a merge do.
#.
-#: advice.c:64
+#: advice.c:70
msgid ""
"Fix them up in the work tree,\n"
"and then use 'git add/rm <file>' as\n"
"or use 'git commit -a'."
msgstr ""
-#: commit.c:47
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr ""
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "cabeçalho não reconhecido: %s%s (%d)"
+
+#: bundle.c:89
+#: builtin/commit.c:753
+#, c-format
+msgid "could not open '%s'"
+msgstr "não é possivel abrir '%s'"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr ""
+
+#: bundle.c:164
+#: sequencer.c:533
+#: sequencer.c:965
+#: builtin/log.c:289
+#: builtin/log.c:719
+#: builtin/log.c:1335
+#: builtin/log.c:1554
+#: builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "rev-list morreu"
+
+#: bundle.c:296
+#: builtin/log.c:1231
+#: builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "argumento não reconhecido: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr ""
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr ""
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "Não foi possível pawn pack-objects"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr ""
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "não consegue crear '%s'"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr ""
+
+#: commit.c:48
#, c-format
msgid "could not parse %s"
msgstr "não consigo parsear %s"
-#: commit.c:49
+#: commit.c:50
#, c-format
msgid "%s %s is not a commit!"
msgstr "%s %s não é um commit!"
msgid "failed to close rev-list's stdin: %s"
msgstr ""
+#: date.c:95
+msgid "in the future"
+msgstr ""
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:154
+#: date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] ""
+msgstr[1] ""
+
#: diff.c:105
#, c-format
msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
"%s"
msgstr ""
-#: diff.c:1336
+#: diff.c:1400
msgid " 0 files changed\n"
msgstr " 0 ficheros modificados\n"
-#: diff.c:1340
+#: diff.c:1404
#, c-format
msgid " %d file changed"
msgid_plural " %d files changed"
msgstr[0] " %d ficheiro modificado"
msgstr[1] " %d ficheiros modificados"
-#: diff.c:1357
+#: diff.c:1421
#, c-format
msgid ", %d insertion(+)"
msgid_plural ", %d insertions(+)"
msgstr[0] ", %d adição(+)"
msgstr[1] ", %d adições(+)"
-#: diff.c:1368
+#: diff.c:1432
#, c-format
msgid ", %d deletion(-)"
msgid_plural ", %d deletions(-)"
msgstr[0] ", %d eliminado(-)"
msgstr[1] ", %d eliminados(-)"
-#: diff.c:3424
+#: diff.c:3478
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
msgid "'%s': short read %s"
msgstr ""
-#: help.c:287
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr ""
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr ""
+
+#: help.c:270
#, c-format
msgid ""
"'%s' appears to be a git command, but we were not\n"
"able to execute it. Maybe git-%s is broken?"
msgstr ""
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr ""
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr ""
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr ""
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+msgstr[1] ""
+
#: remote.c:1607
#, c-format
msgid "Your branch is ahead of '%s' by %d commit.\n"
msgstr[0] ""
msgstr[1] ""
-#: sequencer.c:120
-#: builtin/merge.c:864
-#: builtin/merge.c:985
-#: builtin/merge.c:1095
-#: builtin/merge.c:1105
+#: sequencer.c:121
+#: builtin/merge.c:865
+#: builtin/merge.c:978
+#: builtin/merge.c:1088
+#: builtin/merge.c:1098
#, c-format
msgid "Could not open '%s' for writing"
msgstr ""
-#: sequencer.c:122
-#: builtin/merge.c:334
-#: builtin/merge.c:867
-#: builtin/merge.c:1097
-#: builtin/merge.c:1110
+#: sequencer.c:123
+#: builtin/merge.c:333
+#: builtin/merge.c:868
+#: builtin/merge.c:1090
+#: builtin/merge.c:1103
#, c-format
msgid "Could not write to '%s'"
msgstr "Não foi possível escrever para '%s'"
-#: sequencer.c:143
+#: sequencer.c:144
msgid ""
"after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"
msgstr ""
-#: sequencer.c:146
+#: sequencer.c:147
msgid ""
"after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'\n"
"and commit the result with 'git commit'"
msgstr ""
-#: sequencer.c:159
-#: sequencer.c:685
-#: sequencer.c:768
+#: sequencer.c:160
+#: sequencer.c:741
+#: sequencer.c:824
#, c-format
msgid "Could not write to %s"
msgstr "Não foi possível gravar para %s"
-#: sequencer.c:162
+#: sequencer.c:163
#, c-format
msgid "Error wrapping up %s"
msgstr ""
-#: sequencer.c:177
+#: sequencer.c:178
msgid "Your local changes would be overwritten by cherry-pick."
msgstr ""
-#: sequencer.c:179
+#: sequencer.c:180
msgid "Your local changes would be overwritten by revert."
msgstr ""
-#: sequencer.c:182
+#: sequencer.c:183
msgid "Commit your changes or stash them to proceed."
msgstr ""
#. TRANSLATORS: %s will be "revert" or "cherry-pick"
-#: sequencer.c:232
+#: sequencer.c:233
#, c-format
msgid "%s: Unable to write new index file"
msgstr ""
-#: sequencer.c:298
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr ""
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr ""
+
+#: sequencer.c:323
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Não foi possível analisar commit %s\n"
+
+#: sequencer.c:328
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "Não foi possível analisar commit parent %s\n"
+
+#: sequencer.c:358
msgid "Your index file is unmerged."
msgstr "O seu ficheiro de índice é não fundido."
-#: sequencer.c:301
+#: sequencer.c:361
msgid "You do not have a valid HEAD"
msgstr "Você não tem uma HEAD válida"
-#: sequencer.c:316
+#: sequencer.c:376
#, c-format
msgid "Commit %s is a merge but no -m option was given."
msgstr ""
-#: sequencer.c:324
+#: sequencer.c:384
#, c-format
msgid "Commit %s does not have parent %d"
msgstr ""
-#: sequencer.c:328
+#: sequencer.c:388
#, c-format
msgid "Mainline was specified but commit %s is not a merge."
msgstr ""
#. TRANSLATORS: The first %s will be "revert" or
#. "cherry-pick", the second %s a SHA1
-#: sequencer.c:339
+#: sequencer.c:399
#, c-format
msgid "%s: cannot parse parent commit %s"
msgstr ""
-#: sequencer.c:343
+#: sequencer.c:403
#, c-format
msgid "Cannot get commit message for %s"
msgstr "Não é possível obter mensagem commit para %s"
-#: sequencer.c:427
+#: sequencer.c:491
#, c-format
msgid "could not revert %s... %s"
msgstr ""
-#: sequencer.c:428
+#: sequencer.c:492
#, c-format
msgid "could not apply %s... %s"
msgstr ""
-#: sequencer.c:450
-#: sequencer.c:909
-#: builtin/log.c:288
-#: builtin/log.c:713
-#: builtin/log.c:1329
-#: builtin/log.c:1548
-#: builtin/merge.c:348
-#: builtin/shortlog.c:181
-msgid "revision walk setup failed"
-msgstr ""
-
-#: sequencer.c:453
+#: sequencer.c:536
msgid "empty commit set passed"
msgstr "passado commit com o set vazio"
-#: sequencer.c:461
+#: sequencer.c:544
#, c-format
msgid "git %s: failed to read the index"
msgstr ""
-#: sequencer.c:466
+#: sequencer.c:549
#, c-format
msgid "git %s: failed to refresh the index"
msgstr ""
-#: sequencer.c:551
+#: sequencer.c:607
#, c-format
msgid "Cannot %s during a %s"
msgstr "Não foi possível abrir %s durante um %s"
-#: sequencer.c:573
+#: sequencer.c:629
#, c-format
msgid "Could not parse line %d."
msgstr "Não foi possível parsear linha %d."
-#: sequencer.c:578
+#: sequencer.c:634
msgid "No commits parsed."
msgstr "Nenhum commit parseado."
-#: sequencer.c:591
+#: sequencer.c:647
#, c-format
msgid "Could not open %s"
msgstr "Não foi possível abrir %s"
-#: sequencer.c:595
+#: sequencer.c:651
#, c-format
msgid "Could not read %s."
msgstr "Não foi possível ler %s."
-#: sequencer.c:602
+#: sequencer.c:658
#, c-format
msgid "Unusable instruction sheet: %s"
msgstr ""
-#: sequencer.c:630
+#: sequencer.c:686
#, c-format
msgid "Invalid key: %s"
msgstr ""
-#: sequencer.c:633
+#: sequencer.c:689
#, c-format
msgid "Invalid value for %s: %s"
msgstr "Valor inválido para %s: %s"
-#: sequencer.c:645
+#: sequencer.c:701
#, c-format
msgid "Malformed options sheet: %s"
msgstr ""
-#: sequencer.c:666
+#: sequencer.c:722
msgid "a cherry-pick or revert is already in progress"
msgstr ""
-#: sequencer.c:667
+#: sequencer.c:723
msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
msgstr ""
-#: sequencer.c:671
+#: sequencer.c:727
#, c-format
msgid "Could not create sequencer directory %s"
msgstr ""
-#: sequencer.c:687
-#: sequencer.c:772
+#: sequencer.c:743
+#: sequencer.c:828
#, c-format
msgid "Error wrapping up %s."
msgstr ""
-#: sequencer.c:706
-#: sequencer.c:840
+#: sequencer.c:762
+#: sequencer.c:896
msgid "no cherry-pick or revert in progress"
msgstr ""
-#: sequencer.c:708
+#: sequencer.c:764
msgid "cannot resolve HEAD"
msgstr ""
-#: sequencer.c:710
+#: sequencer.c:766
msgid "cannot abort from a branch yet to be born"
msgstr ""
-#: sequencer.c:732
+#: sequencer.c:788
+#: builtin/apply.c:3682
#, c-format
msgid "cannot open %s: %s"
msgstr "não foi possível abrir %s: %s"
-#: sequencer.c:735
+#: sequencer.c:791
#, c-format
msgid "cannot read %s: %s"
msgstr "não foi possível ler %s: %s"
-#: sequencer.c:736
+#: sequencer.c:792
msgid "unexpected end of file"
msgstr ""
-#: sequencer.c:742
+#: sequencer.c:798
#, c-format
msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
msgstr ""
-#: sequencer.c:765
+#: sequencer.c:821
#, c-format
msgid "Could not format %s."
msgstr "Não foi possível formatear %s."
-#: sequencer.c:927
+#: sequencer.c:983
msgid "Can't revert as initial commit"
msgstr ""
-#: sequencer.c:928
+#: sequencer.c:984
msgid "Can't cherry-pick into empty head"
msgstr ""
-#: wt-status.c:134
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr ""
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Não existe rama '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr ""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
+#: wt-status.c:135
msgid "Unmerged paths:"
msgstr "caminhos não fundidos:"
-#: wt-status.c:140
-#: wt-status.c:157
+#: wt-status.c:141
+#: wt-status.c:158
#, c-format
msgid " (use \"git reset %s <file>...\" to unstage)"
msgstr ""
-#: wt-status.c:142
-#: wt-status.c:159
+#: wt-status.c:143
+#: wt-status.c:160
msgid " (use \"git rm --cached <file>...\" to unstage)"
msgstr ""
-#: wt-status.c:143
+#: wt-status.c:144
msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
msgstr " (usa \"git add/rm <ficheiro>...\" para marcar como resolvido)"
-#: wt-status.c:151
+#: wt-status.c:152
msgid "Changes to be committed:"
msgstr "Mudanças a serem commitadas"
-#: wt-status.c:169
+#: wt-status.c:170
msgid "Changes not staged for commit:"
msgstr ""
-#: wt-status.c:173
+#: wt-status.c:174
msgid " (use \"git add <file>...\" to update what will be committed)"
msgstr " (usa \"git add <ficheiro>...\" para actualizar o que vai ser commitado)"
-#: wt-status.c:175
+#: wt-status.c:176
msgid " (use \"git add/rm <file>...\" to update what will be committed)"
msgstr " (usa \"git add/rm <ficheiro>...\" para actualizar o que vai ser commitado)"
-#: wt-status.c:176
+#: wt-status.c:177
msgid " (use \"git checkout -- <file>...\" to discard changes in working directory)"
msgstr ""
-#: wt-status.c:178
+#: wt-status.c:179
msgid " (commit or discard the untracked or modified content in submodules)"
msgstr ""
-#: wt-status.c:187
+#: wt-status.c:188
#, c-format
msgid "%s files:"
msgstr "%s ficheros:"
-#: wt-status.c:190
+#: wt-status.c:191
#, c-format
msgid " (use \"git %s <file>...\" to include in what will be committed)"
msgstr ""
-#: wt-status.c:207
+#: wt-status.c:208
msgid "bug"
msgstr "erro"
-#: wt-status.c:212
+#: wt-status.c:213
msgid "both deleted:"
msgstr "eliminados em ambos:"
-#: wt-status.c:213
+#: wt-status.c:214
msgid "added by us:"
msgstr "adicionado por nós:"
-#: wt-status.c:214
+#: wt-status.c:215
msgid "deleted by them:"
msgstr "eliminados por eles:"
-#: wt-status.c:215
+#: wt-status.c:216
msgid "added by them:"
msgstr "adicionados por eles:"
-#: wt-status.c:216
+#: wt-status.c:217
msgid "deleted by us:"
msgstr "eliminados por nós:"
-#: wt-status.c:217
+#: wt-status.c:218
msgid "both added:"
msgstr "adicionados em ambos:"
-#: wt-status.c:218
+#: wt-status.c:219
msgid "both modified:"
msgstr "modificados em ambos:"
-#: wt-status.c:248
+#: wt-status.c:249
msgid "new commits, "
msgstr "novos commits, "
-#: wt-status.c:250
+#: wt-status.c:251
msgid "modified content, "
msgstr "conteúdo modificado, "
-#: wt-status.c:252
+#: wt-status.c:253
msgid "untracked content, "
msgstr "conteúdo não seguido"
-#: wt-status.c:266
+#: wt-status.c:267
#, c-format
msgid "new file: %s"
msgstr "novo ficheiro: %s"
-#: wt-status.c:269
+#: wt-status.c:270
#, c-format
msgid "copied: %s -> %s"
msgstr "copiado: %s -> %s"
-#: wt-status.c:272
+#: wt-status.c:273
#, c-format
msgid "deleted: %s"
msgstr "eliminado: %s"
-#: wt-status.c:275
+#: wt-status.c:276
#, c-format
msgid "modified: %s"
msgstr "modificado: %s"
-#: wt-status.c:278
+#: wt-status.c:279
#, c-format
msgid "renamed: %s -> %s"
msgstr "mudado de nome: %s -> %s"
-#: wt-status.c:281
+#: wt-status.c:282
#, c-format
msgid "typechange: %s"
msgstr ""
-#: wt-status.c:284
+#: wt-status.c:285
#, c-format
msgid "unknown: %s"
msgstr "desconhecido: %s"
-#: wt-status.c:287
+#: wt-status.c:288
#, c-format
msgid "unmerged: %s"
msgstr "não fundidos: %s"
-#: wt-status.c:290
+#: wt-status.c:291
#, c-format
msgid "bug: unhandled diff status %c"
msgstr ""
-#: wt-status.c:713
+#: wt-status.c:737
msgid "On branch "
msgstr "Na rama"
-#: wt-status.c:720
+#: wt-status.c:744
msgid "Not currently on any branch."
msgstr "Não está em nenhuma rama."
-#: wt-status.c:731
+#: wt-status.c:755
msgid "Initial commit"
msgstr "Commit inicial"
-#: wt-status.c:745
+#: wt-status.c:769
msgid "Untracked"
msgstr "Não seguido"
-#: wt-status.c:747
+#: wt-status.c:771
msgid "Ignored"
msgstr "Ignorado"
-#: wt-status.c:749
+#: wt-status.c:773
#, c-format
msgid "Untracked files not listed%s"
msgstr ""
-#: wt-status.c:751
+#: wt-status.c:775
msgid " (use -u option to show untracked files)"
msgstr ""
-#: wt-status.c:757
+#: wt-status.c:781
msgid "No changes"
msgstr "Sem mudanças"
-#: wt-status.c:761
+#: wt-status.c:785
#, c-format
msgid "no changes added to commit%s\n"
msgstr "nenhuma alteração adicionado ao commit%s\n"
-#: wt-status.c:763
+#: wt-status.c:787
msgid " (use \"git add\" and/or \"git commit -a\")"
msgstr " (usa \"git add\" e/ou \"git commit -a\")"
-#: wt-status.c:765
+#: wt-status.c:789
#, c-format
msgid "nothing added to commit but untracked files present%s\n"
msgstr ""
-#: wt-status.c:767
+#: wt-status.c:791
msgid " (use \"git add\" to track)"
msgstr " (usa \"git add\" para seguir)"
-#: wt-status.c:769
-#: wt-status.c:772
-#: wt-status.c:775
+#: wt-status.c:793
+#: wt-status.c:796
+#: wt-status.c:799
#, c-format
msgid "nothing to commit%s\n"
msgstr "nada para fazer commit%s\n"
-#: wt-status.c:770
+#: wt-status.c:794
msgid " (create/copy files and use \"git add\" to track)"
msgstr ""
-#: wt-status.c:773
+#: wt-status.c:797
msgid " (use -u to show untracked files)"
msgstr ""
-#: wt-status.c:776
+#: wt-status.c:800
msgid " (working directory clean)"
msgstr " (directório de trabalho vacio)"
-#: wt-status.c:884
+#: wt-status.c:908
msgid "HEAD (no branch)"
msgstr "HEAD (Não é rama)"
-#: wt-status.c:890
+#: wt-status.c:914
msgid "Initial commit on "
msgstr "Commit inicial em "
-#: wt-status.c:905
+#: wt-status.c:929
msgid "behind "
msgstr "atrás "
-#: wt-status.c:908
-#: wt-status.c:911
+#: wt-status.c:932
+#: wt-status.c:935
msgid "ahead "
msgstr "a frente "
-#: wt-status.c:913
+#: wt-status.c:937
msgid ", behind "
msgstr ", atrás "
msgstr ""
#: builtin/add.c:67
-#: builtin/commit.c:298
+#: builtin/commit.c:282
msgid "updating files failed"
msgstr "Falou a atualização dos ficheiros"
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr ""
-#: builtin/add.c:352
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr ""
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "nenhum ficheiros adicionado"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "falhou a adicionar ficheiros"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr ""
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr ""
+
+#: builtin/add.c:420
+#: builtin/clean.c:95
+#: builtin/commit.c:342
+#: builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "ficheiro index corrupto"
+
+#: builtin/add.c:476
+#: builtin/apply.c:4093
+#: builtin/mv.c:229
+#: builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr ""
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "espaço em braco não reconhecido: '%s'"
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr ""
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr ""
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr ""
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:934
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr ""
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - inconsistent %s filename on line %d"
+msgstr ""
+
+#: builtin/apply.c:944
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr ""
+
+#: builtin/apply.c:1387
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1444
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1461
+#, c-format
+msgid "git diff header lacks filename information when removing %d leading pathname component (line %d)"
+msgid_plural "git diff header lacks filename information when removing %d leading pathname components (line %d)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:1621
+msgid "new file depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1623
+msgid "deleted file still has contents"
+msgstr ""
+
+#: builtin/apply.c:1649
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1685
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1687
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr ""
+
+#: builtin/apply.c:1690
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr ""
+
+#: builtin/apply.c:1836
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr ""
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1865
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1951
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr ""
+
+#: builtin/apply.c:2041
+#, c-format
+msgid "unable to read symlink %s"
+msgstr ""
+
+#: builtin/apply.c:2045
+#, c-format
+msgid "unable to open or read %s"
+msgstr "Não foi possível abrir o ler %s"
+
+#: builtin/apply.c:2116
+msgid "oops"
+msgstr ""
+
+#: builtin/apply.c:2638
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "começo de linha inválido: '%c'"
+
+#: builtin/apply.c:2756
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:2768
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr ""
+
+#: builtin/apply.c:2774
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+
+#: builtin/apply.c:2793
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr ""
+
+#: builtin/apply.c:2896
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr ""
+
+#: builtin/apply.c:2902
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr ""
+
+#: builtin/apply.c:2923
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr ""
+
+#: builtin/apply.c:3038
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr ""
+
+#: builtin/apply.c:3045
+#: builtin/apply.c:3062
+#, c-format
+msgid "read of %s failed"
+msgstr "ler %s falhou"
+
+#: builtin/apply.c:3077
+msgid "removal patch leaves file contents"
+msgstr ""
+
+#: builtin/apply.c:3098
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s: já existe no espaço de trabalho"
+
+#: builtin/apply.c:3136
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr ""
+
+#: builtin/apply.c:3141
+#: builtin/apply.c:3172
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3152
+#, c-format
+msgid "%s: does not exist in index"
+msgstr ""
+
+#: builtin/apply.c:3166
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s: não tem correspondencia ao index"
+
+#: builtin/apply.c:3183
+#, c-format
+msgid "%s: wrong type"
+msgstr ""
+
+#: builtin/apply.c:3185
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr ""
+
+#: builtin/apply.c:3240
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s: já existe no indíce"
+
+#: builtin/apply.c:3259
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)%s%s"
+msgstr ""
+
+#: builtin/apply.c:3265
+#, c-format
+msgid "%s: patch does not apply"
+msgstr ""
+
+#: builtin/apply.c:3278
+#, c-format
+msgid "Checking patch %s..."
+msgstr ""
+
+#: builtin/apply.c:3333
+#: builtin/checkout.c:212
+#: builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr ""
+
+#: builtin/apply.c:3476
+#, c-format
+msgid "unable to remove %s from index"
+msgstr ""
+
+#: builtin/apply.c:3503
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr ""
+
+#: builtin/apply.c:3507
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "não é possivel 'stat' o novo ficheiro creado '%s'"
+
+#: builtin/apply.c:3512
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr ""
+
+#: builtin/apply.c:3515
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr ""
+
+#: builtin/apply.c:3548
+#, c-format
+msgid "closing file '%s'"
+msgstr "fechar fichero '%s'"
+
+#: builtin/apply.c:3597
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr ""
+
+#: builtin/apply.c:3653
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr ""
+
+#: builtin/apply.c:3661
+msgid "internal error"
+msgstr ""
+
+#. Say this even without --verbose
+#: builtin/apply.c:3664
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:3674
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr ""
+
+#: builtin/apply.c:3695
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr ""
+
+#: builtin/apply.c:3698
#, c-format
-msgid "Use -f if you really want to add them.\n"
+msgid "Rejected hunk #%d."
msgstr ""
-#: builtin/add.c:353
-msgid "no files added"
-msgstr "nenhum ficheiros adicionado"
+#: builtin/apply.c:3829
+msgid "unrecognized input"
+msgstr "entrada não reconhecida"
-#: builtin/add.c:359
-msgid "adding files failed"
-msgstr "falhou a adicionar ficheiros"
+#: builtin/apply.c:3840
+msgid "unable to read index file"
+msgstr "Não foi possível ler o fichero indíce"
-#: builtin/add.c:391
-msgid "-A and -u are mutually incompatible"
-msgstr ""
+#: builtin/apply.c:4035
+msgid "--index outside a repository"
+msgstr "--index fora de um repositorio"
-#: builtin/add.c:393
-msgid "Option --ignore-missing can only be used together with --dry-run"
-msgstr ""
+#: builtin/apply.c:4038
+msgid "--cached outside a repository"
+msgstr "--cached fora de um repositorio"
-#: builtin/add.c:413
+#: builtin/apply.c:4054
#, c-format
-msgid "Nothing specified, nothing added.\n"
-msgstr ""
+msgid "can't open patch '%s'"
+msgstr "não é possivel abrir patch '%s'"
-#: builtin/add.c:414
+#: builtin/apply.c:4068
#, c-format
-msgid "Maybe you wanted to say 'git add .'?\n"
-msgstr ""
-
-#: builtin/add.c:420
-#: builtin/clean.c:95
-#: builtin/commit.c:358
-#: builtin/mv.c:82
-#: builtin/rm.c:162
-msgid "index file corrupt"
-msgstr "ficheiro index corrupto"
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] ""
+msgstr[1] ""
-#: builtin/add.c:476
-#: builtin/mv.c:229
-#: builtin/rm.c:260
-msgid "Unable to write new index file"
-msgstr ""
+#: builtin/apply.c:4074
+#: builtin/apply.c:4084
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] ""
+msgstr[1] ""
#: builtin/archive.c:17
#, c-format
msgid "git archive: expected a flush"
msgstr ""
-#: builtin/branch.c:137
+#: builtin/branch.c:144
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."
msgstr ""
-#: builtin/branch.c:141
+#: builtin/branch.c:148
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
" '%s', even though it is merged to HEAD."
msgstr ""
-#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
-msgid "remote "
-msgstr "remota"
-
-#: builtin/branch.c:171
+#: builtin/branch.c:180
msgid "cannot use -a with -d"
msgstr "Não é possível usar -a com um -d"
-#: builtin/branch.c:177
+#: builtin/branch.c:186
msgid "Couldn't look up commit object for HEAD"
msgstr ""
-#: builtin/branch.c:182
+#: builtin/branch.c:191
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr ""
-#: builtin/branch.c:192
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "rama remota '%s não encontrada."
+
+#: builtin/branch.c:203
#, c-format
-msgid "%sbranch '%s' not found."
-msgstr "%sbranch '%s' não encontrado."
+msgid "branch '%s' not found."
+msgstr "rama '%s' não encontrado."
-#: builtin/branch.c:200
+#: builtin/branch.c:210
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr ""
-#: builtin/branch.c:206
+#: builtin/branch.c:216
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, run 'git branch -D %s'."
msgstr ""
-#: builtin/branch.c:214
+#: builtin/branch.c:225
#, c-format
-msgid "Error deleting %sbranch '%s'"
+msgid "Error deleting remote branch '%s'"
msgstr ""
-#: builtin/branch.c:219
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Erro a eliminar rama '%s'"
+
+#: builtin/branch.c:233
#, c-format
-msgid "Deleted %sbranch %s (was %s).\n"
+msgid "Deleted remote branch %s (was %s).\n"
msgstr ""
-#: builtin/branch.c:224
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Eliminar rama %s (era %s).\n"
+
+#: builtin/branch.c:239
msgid "Update of config-file failed"
msgstr ""
-#: builtin/branch.c:322
+#: builtin/branch.c:337
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr ""
-#: builtin/branch.c:394
+#: builtin/branch.c:409
#, c-format
msgid "behind %d] "
msgstr "atrás %d] "
-#: builtin/branch.c:396
+#: builtin/branch.c:411
#, c-format
msgid "ahead %d] "
msgstr "a frente %d] "
-#: builtin/branch.c:398
+#: builtin/branch.c:413
#, c-format
msgid "ahead %d, behind %d] "
msgstr "a frente %d, atrás %d] "
-#: builtin/branch.c:501
+#: builtin/branch.c:521
msgid "(no branch)"
msgstr "(não é rama)"
-#: builtin/branch.c:566
+#: builtin/branch.c:586
msgid "some refs could not be read"
msgstr ""
-#: builtin/branch.c:579
+#: builtin/branch.c:599
msgid "cannot rename the current branch while not on any."
msgstr ""
-#: builtin/branch.c:589
+#: builtin/branch.c:609
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "Nome da rama inválida: '%s'"
-#: builtin/branch.c:604
+#: builtin/branch.c:624
msgid "Branch rename failed"
msgstr "Falhou renomeação da rama"
-#: builtin/branch.c:608
+#: builtin/branch.c:628
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "Renomeado uma rama erronea '%s'"
-#: builtin/branch.c:612
+#: builtin/branch.c:632
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr ""
-#: builtin/branch.c:619
+#: builtin/branch.c:639
msgid "Branch is renamed, but update of config-file failed"
msgstr ""
-#: builtin/branch.c:634
+#: builtin/branch.c:654
#, c-format
msgid "malformed object name %s"
msgstr ""
-#: builtin/branch.c:658
+#: builtin/branch.c:678
#, c-format
-msgid "could not write branch description template: %s\n"
+msgid "could not write branch description template: %s"
msgstr ""
-#: builtin/branch.c:746
+#: builtin/branch.c:769
msgid "Failed to resolve HEAD as a valid ref."
msgstr ""
-#: builtin/branch.c:751
+#: builtin/branch.c:774
#: builtin/clone.c:558
msgid "HEAD not found below refs/heads!"
msgstr ""
-#: builtin/branch.c:809
+#: builtin/branch.c:794
+msgid "--column and --verbose are incompatible"
+msgstr "--column e --verbose são incompatíveis"
+
+#: builtin/branch.c:843
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
msgid "Unable to add merge result for '%s'"
msgstr ""
-#: builtin/checkout.c:212
-#: builtin/reset.c:158
-#, c-format
-msgid "make_cache_entry failed for path '%s'"
-msgstr ""
-
#: builtin/checkout.c:234
#: builtin/checkout.c:392
msgid "corrupt index file"
#: builtin/checkout.c:302
#: builtin/checkout.c:498
#: builtin/clone.c:583
-#: builtin/merge.c:811
+#: builtin/merge.c:812
msgid "unable to write new index file"
msgstr ""
msgid "Can not do reflog for '%s'\n"
msgstr ""
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
msgid "HEAD is now at"
msgstr "HEAD é agora em "
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
#, c-format
msgid "Reset branch '%s'\n"
msgstr "Reset rama '%s'\n"
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
#, c-format
msgid "Already on '%s'\n"
msgstr "Já em '%s'\n"
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr ""
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "Mudado para a nova rama '%s'\n"
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "Mudado para a rama '%s'\n"
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
#, c-format
msgid " ... and %d more.\n"
msgstr " ... e %d mais.\n"
#. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
msgstr[0] ""
msgstr[1] ""
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
"\n"
msgstr ""
-#: builtin/checkout.c:692
+#: builtin/checkout.c:693
msgid "internal error in revision walk"
msgstr ""
-#: builtin/checkout.c:696
+#: builtin/checkout.c:697
msgid "Previous HEAD position was"
msgstr ""
-#: builtin/checkout.c:722
+#: builtin/checkout.c:723
msgid "You are on a branch yet to be born"
msgstr ""
#. case (1)
-#: builtin/checkout.c:853
+#: builtin/checkout.c:854
#, c-format
msgid "invalid reference: %s"
msgstr ""
#. case (1): want a tree
-#: builtin/checkout.c:892
+#: builtin/checkout.c:893
#, c-format
msgid "reference is not a tree: %s"
msgstr ""
-#: builtin/checkout.c:972
+#: builtin/checkout.c:973
msgid "-B cannot be used with -b"
msgstr ""
-#: builtin/checkout.c:981
+#: builtin/checkout.c:982
msgid "--patch is incompatible with all other options"
msgstr ""
-#: builtin/checkout.c:984
+#: builtin/checkout.c:985
msgid "--detach cannot be used with -b/-B/--orphan"
msgstr ""
-#: builtin/checkout.c:986
+#: builtin/checkout.c:987
msgid "--detach cannot be used with -t"
msgstr ""
-#: builtin/checkout.c:992
+#: builtin/checkout.c:993
msgid "--track needs a branch name"
msgstr ""
-#: builtin/checkout.c:999
+#: builtin/checkout.c:1000
msgid "Missing branch name; try -b"
msgstr ""
-#: builtin/checkout.c:1005
+#: builtin/checkout.c:1006
msgid "--orphan and -b|-B are mutually exclusive"
msgstr ""
-#: builtin/checkout.c:1007
+#: builtin/checkout.c:1008
msgid "--orphan cannot be used with -t"
msgstr ""
-#: builtin/checkout.c:1017
+#: builtin/checkout.c:1018
msgid "git checkout: -f and -m are incompatible"
msgstr ""
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1052
msgid "invalid path specification"
msgstr ""
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1060
#, c-format
msgid ""
"git checkout: updating paths is incompatible with switching branches.\n"
"Did you intend to checkout '%s' which can not be resolved as commit?"
msgstr ""
-#: builtin/checkout.c:1061
+#: builtin/checkout.c:1062
msgid "git checkout: updating paths is incompatible with switching branches."
msgstr ""
-#: builtin/checkout.c:1066
+#: builtin/checkout.c:1067
msgid "git checkout: --detach does not take a path argument"
msgstr ""
-#: builtin/checkout.c:1069
+#: builtin/checkout.c:1070
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr ""
-#: builtin/checkout.c:1088
+#: builtin/checkout.c:1089
msgid "Cannot switch branch to a non-commit."
msgstr ""
-#: builtin/checkout.c:1091
+#: builtin/checkout.c:1092
msgid "--ours/--theirs is incompatible with switching branches."
msgstr ""
#: builtin/clone.c:302
#, c-format
msgid "failed to open '%s'"
-msgstr ""
+msgstr "falhou a abrir '%s'"
#: builtin/clone.c:306
#, c-format
msgid "failed to create directory '%s'"
-msgstr ""
+msgstr "falhou a criar o directório '%s'"
#: builtin/clone.c:308
#: builtin/diff.c:75
#: builtin/clone.c:350
#, c-format
msgid "failed to copy file to '%s'"
-msgstr ""
+msgstr "falhou a copiar o ficheiro para '%s'"
#: builtin/clone.c:373
#, c-format
msgid "You appear to have cloned an empty repository."
msgstr ""
-#: builtin/commit.c:42
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr ""
+
+#: builtin/commit.c:43
msgid ""
"Your name and email address were configured automatically based\n"
"on your username and hostname. Please check that they are accurate.\n"
"\n"
" git commit --amend --reset-author\n"
-#: builtin/commit.c:54
+#: builtin/commit.c:55
msgid ""
"You asked to amend the most recent commit, but doing so would make\n"
"it empty. You can repeat your command with --allow-empty, or you can\n"
"remove the commit entirely with \"git reset HEAD^\".\n"
msgstr ""
-#: builtin/commit.c:59
+#: builtin/commit.c:60
msgid ""
"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
"If you wish to commit it anyway, use:\n"
"Otherwise, please use 'git reset'\n"
msgstr ""
-#: builtin/commit.c:205
-#: builtin/reset.c:33
-msgid "merge"
-msgstr "juntar"
-
-#: builtin/commit.c:208
-msgid "cherry-pick"
-msgstr "cherry-pick"
-
-#: builtin/commit.c:325
+#: builtin/commit.c:309
msgid "failed to unpack HEAD tree object"
msgstr ""
-#: builtin/commit.c:367
+#: builtin/commit.c:351
msgid "unable to create temporary index"
msgstr ""
-#: builtin/commit.c:373
+#: builtin/commit.c:357
msgid "interactive add failed"
msgstr "falhou adicionar interativo"
-#: builtin/commit.c:406
-#: builtin/commit.c:427
-#: builtin/commit.c:473
+#: builtin/commit.c:390
+#: builtin/commit.c:411
+#: builtin/commit.c:461
msgid "unable to write new_index file"
msgstr ""
-#: builtin/commit.c:457
-#, c-format
-msgid "cannot do a partial commit during a %s."
+#: builtin/commit.c:442
+msgid "cannot do a partial commit during a merge."
+msgstr ""
+
+#: builtin/commit.c:444
+msgid "cannot do a partial commit during a cherry-pick."
msgstr ""
-#: builtin/commit.c:466
+#: builtin/commit.c:454
msgid "cannot read the index"
msgstr "não foi possível ler o indíce"
-#: builtin/commit.c:486
+#: builtin/commit.c:474
msgid "unable to write temporary index file"
msgstr ""
-#: builtin/commit.c:550
-#: builtin/commit.c:556
+#: builtin/commit.c:549
+#: builtin/commit.c:555
#, c-format
msgid "invalid commit: %s"
msgstr "commit inválido: %s"
-#: builtin/commit.c:579
+#: builtin/commit.c:578
msgid "malformed --author parameter"
msgstr ""
-#: builtin/commit.c:635
+#: builtin/commit.c:639
#, c-format
msgid "Malformed ident string: '%s'"
msgstr ""
-#: builtin/commit.c:670
-#: builtin/commit.c:703
-#: builtin/commit.c:1000
+#: builtin/commit.c:677
+#: builtin/commit.c:710
+#: builtin/commit.c:1024
#, c-format
msgid "could not lookup commit %s"
msgstr ""
-#: builtin/commit.c:682
+#: builtin/commit.c:689
#: builtin/shortlog.c:296
#, c-format
msgid "(reading log message from standard input)\n"
msgstr ""
-#: builtin/commit.c:684
+#: builtin/commit.c:691
msgid "could not read log from standard input"
msgstr ""
-#: builtin/commit.c:688
+#: builtin/commit.c:695
#, c-format
msgid "could not read log file '%s'"
msgstr "não é possivel ler o ficheiro de log '%s'"
-#: builtin/commit.c:694
+#: builtin/commit.c:701
msgid "commit has empty message"
msgstr "a mensagem do commit está vazia"
-#: builtin/commit.c:710
+#: builtin/commit.c:717
msgid "could not read MERGE_MSG"
msgstr "não é possivel ler MERGE_MSG"
-#: builtin/commit.c:714
+#: builtin/commit.c:721
msgid "could not read SQUASH_MSG"
msgstr "não é possivel ler SQUASH_MSG"
-#: builtin/commit.c:718
+#: builtin/commit.c:725
#, c-format
msgid "could not read '%s'"
msgstr "não é possivel ler '%s'"
-#: builtin/commit.c:746
-#, c-format
-msgid "could not open '%s'"
-msgstr "não é possivel abrir '%s'"
-
-#: builtin/commit.c:770
+#: builtin/commit.c:777
msgid "could not write commit template"
msgstr ""
-#: builtin/commit.c:783
+#: builtin/commit.c:788
#, c-format
msgid ""
"\n"
-"It looks like you may be committing a %s.\n"
+"It looks like you may be committing a merge.\n"
"If this is not correct, please remove the file\n"
"\t%s\n"
"and try again.\n"
msgstr ""
-#: builtin/commit.c:796
-msgid "Please enter the commit message for your changes."
-msgstr "Por favor insira a mensagem de commit das suas alterações."
+#: builtin/commit.c:793
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
-#: builtin/commit.c:799
+#: builtin/commit.c:805
msgid ""
-" Lines starting\n"
+"Please enter the commit message for your changes. Lines starting\n"
"with '#' will be ignored, and an empty message aborts the commit.\n"
msgstr ""
-#: builtin/commit.c:804
+#: builtin/commit.c:810
msgid ""
-" Lines starting\n"
+"Please enter the commit message for your changes. Lines starting\n"
"with '#' will be kept; you may remove them yourself if you want to.\n"
"An empty message aborts the commit.\n"
msgstr ""
-#: builtin/commit.c:816
+#: builtin/commit.c:823
#, c-format
msgid "%sAuthor: %s"
msgstr "%sAutor: %s"
-#: builtin/commit.c:823
+#: builtin/commit.c:830
#, c-format
msgid "%sCommitter: %s"
msgstr "%sCommitador: %s"
-#: builtin/commit.c:843
+#: builtin/commit.c:850
msgid "Cannot read index"
msgstr ""
-#: builtin/commit.c:880
+#: builtin/commit.c:887
msgid "Error building trees"
msgstr ""
-#: builtin/commit.c:895
-#: builtin/tag.c:357
+#: builtin/commit.c:902
+#: builtin/tag.c:361
#, c-format
msgid "Please supply the message using either -m or -F option.\n"
msgstr ""
-#: builtin/commit.c:975
+#: builtin/commit.c:999
#, c-format
msgid "No existing author found with '%s'"
msgstr ""
-#: builtin/commit.c:990
-#: builtin/commit.c:1182
+#: builtin/commit.c:1014
+#: builtin/commit.c:1214
#, c-format
msgid "Invalid untracked files mode '%s'"
msgstr ""
-#: builtin/commit.c:1030
+#: builtin/commit.c:1054
msgid "Using both --reset-author and --author does not make sense"
msgstr ""
-#: builtin/commit.c:1041
+#: builtin/commit.c:1065
msgid "You have nothing to amend."
msgstr "Você não tem nada a corregir."
-#: builtin/commit.c:1043
-#, c-format
-msgid "You are in the middle of a %s -- cannot amend."
+#: builtin/commit.c:1068
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1070
+msgid "You are in the middle of a cherry-pick -- cannot amend."
msgstr ""
-#: builtin/commit.c:1045
+#: builtin/commit.c:1073
msgid "Options --squash and --fixup cannot be used together"
msgstr ""
-#: builtin/commit.c:1055
+#: builtin/commit.c:1083
msgid "Only one of -c/-C/-F/--fixup can be used."
msgstr ""
-#: builtin/commit.c:1057
+#: builtin/commit.c:1085
msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
msgstr ""
-#: builtin/commit.c:1063
+#: builtin/commit.c:1093
msgid "--reset-author can be used only with -C, -c or --amend."
msgstr ""
-#: builtin/commit.c:1080
+#: builtin/commit.c:1110
msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
msgstr ""
-#: builtin/commit.c:1082
+#: builtin/commit.c:1112
msgid "No paths with --include/--only does not make sense."
msgstr ""
-#: builtin/commit.c:1084
+#: builtin/commit.c:1114
msgid "Clever... amending the last one with dirty index."
msgstr ""
-#: builtin/commit.c:1086
+#: builtin/commit.c:1116
msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
msgstr ""
-#: builtin/commit.c:1096
-#: builtin/tag.c:556
+#: builtin/commit.c:1126
+#: builtin/tag.c:577
#, c-format
msgid "Invalid cleanup mode %s"
msgstr ""
-#: builtin/commit.c:1101
+#: builtin/commit.c:1131
msgid "Paths with -a does not make sense."
msgstr ""
-#: builtin/commit.c:1280
+#: builtin/commit.c:1315
msgid "couldn't look up newly created commit"
msgstr ""
-#: builtin/commit.c:1282
+#: builtin/commit.c:1317
msgid "could not parse newly created commit"
msgstr ""
-#: builtin/commit.c:1323
+#: builtin/commit.c:1358
msgid "detached HEAD"
msgstr ""
-#: builtin/commit.c:1325
+#: builtin/commit.c:1360
msgid " (root-commit)"
msgstr " (root-commit)"
-#: builtin/commit.c:1415
+#: builtin/commit.c:1450
msgid "could not parse HEAD commit"
msgstr ""
-#: builtin/commit.c:1452
+#: builtin/commit.c:1487
#: builtin/merge.c:509
#, c-format
msgid "could not open '%s' for reading"
msgstr ""
-#: builtin/commit.c:1459
+#: builtin/commit.c:1494
#, c-format
msgid "Corrupt MERGE_HEAD file (%s)"
msgstr ""
-#: builtin/commit.c:1466
+#: builtin/commit.c:1501
msgid "could not read MERGE_MODE"
msgstr ""
-#: builtin/commit.c:1485
+#: builtin/commit.c:1520
#, c-format
msgid "could not read commit message: %s"
msgstr ""
-#: builtin/commit.c:1499
+#: builtin/commit.c:1534
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr ""
+
+#: builtin/commit.c:1539
#, c-format
msgid "Aborting commit due to empty commit message.\n"
msgstr ""
-#: builtin/commit.c:1514
-#: builtin/merge.c:935
-#: builtin/merge.c:968
+#: builtin/commit.c:1554
+#: builtin/merge.c:936
+#: builtin/merge.c:961
msgid "failed to write commit object"
msgstr ""
-#: builtin/commit.c:1535
+#: builtin/commit.c:1575
msgid "cannot lock HEAD ref"
msgstr ""
-#: builtin/commit.c:1539
+#: builtin/commit.c:1579
msgid "cannot update HEAD ref"
msgstr ""
-#: builtin/commit.c:1550
+#: builtin/commit.c:1590
msgid ""
"Repository has been updated, but unable to write\n"
"new_index file. Check that disk is not full or quota is\n"
msgid "Couldn't find remote ref HEAD"
msgstr ""
-#: builtin/fetch.c:252
+#: builtin/fetch.c:253
#, c-format
msgid "object %s not found"
-msgstr ""
+msgstr "objecto %s não encontrado"
-#: builtin/fetch.c:258
+#: builtin/fetch.c:259
msgid "[up to date]"
msgstr "[Actualizada]"
-#: builtin/fetch.c:272
+#: builtin/fetch.c:273
#, c-format
msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
msgstr ""
-#: builtin/fetch.c:273
-#: builtin/fetch.c:351
+#: builtin/fetch.c:274
+#: builtin/fetch.c:360
msgid "[rejected]"
msgstr "[rejeitado]"
-#: builtin/fetch.c:284
+#: builtin/fetch.c:285
msgid "[tag update]"
msgstr "[etiqueta actualizada]"
-#: builtin/fetch.c:286
-#: builtin/fetch.c:313
-#: builtin/fetch.c:331
+#: builtin/fetch.c:287
+#: builtin/fetch.c:322
+#: builtin/fetch.c:340
msgid " (unable to update local ref)"
msgstr ""
-#: builtin/fetch.c:298
+#: builtin/fetch.c:305
msgid "[new tag]"
msgstr "[nova etiqueta]"
-#: builtin/fetch.c:302
+#: builtin/fetch.c:308
msgid "[new branch]"
msgstr "[nova rama]"
-#: builtin/fetch.c:347
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[nova ref]"
+
+#: builtin/fetch.c:356
msgid "unable to update local ref"
msgstr ""
-#: builtin/fetch.c:347
+#: builtin/fetch.c:356
msgid "forced update"
msgstr "actualização forçada"
-#: builtin/fetch.c:353
+#: builtin/fetch.c:362
msgid "(non-fast-forward)"
-msgstr ""
+msgstr "(non-fast-forward)"
-#: builtin/fetch.c:384
-#: builtin/fetch.c:676
+#: builtin/fetch.c:393
+#: builtin/fetch.c:685
#, c-format
msgid "cannot open %s: %s\n"
-msgstr ""
+msgstr "não é possivel abrir %s: %s\n"
-#: builtin/fetch.c:393
+#: builtin/fetch.c:402
#, c-format
msgid "%s did not send all necessary objects\n"
msgstr ""
-#: builtin/fetch.c:479
+#: builtin/fetch.c:488
#, c-format
msgid "From %.*s\n"
msgstr "Para %.*s\n"
-#: builtin/fetch.c:490
+#: builtin/fetch.c:499
#, c-format
msgid ""
"some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting branches"
msgstr ""
-#: builtin/fetch.c:540
+#: builtin/fetch.c:549
#, c-format
-msgid " (%s will become dangling)\n"
+msgid " (%s will become dangling)"
msgstr ""
-#: builtin/fetch.c:541
+#: builtin/fetch.c:550
#, c-format
-msgid " (%s has become dangling)\n"
+msgid " (%s has become dangling)"
msgstr ""
-#: builtin/fetch.c:548
+#: builtin/fetch.c:557
msgid "[deleted]"
msgstr "[eliminado]"
-#: builtin/fetch.c:549
+#: builtin/fetch.c:558
+#: builtin/remote.c:1055
msgid "(none)"
msgstr "(nenhum)"
-#: builtin/fetch.c:666
+#: builtin/fetch.c:675
#, c-format
msgid "Refusing to fetch into current branch %s of non-bare repository"
msgstr ""
-#: builtin/fetch.c:700
+#: builtin/fetch.c:709
#, c-format
msgid "Don't know how to fetch from %s"
msgstr ""
-#: builtin/fetch.c:777
+#: builtin/fetch.c:786
#, c-format
msgid "Option \"%s\" value \"%s\" is not valid for %s"
msgstr ""
-#: builtin/fetch.c:780
+#: builtin/fetch.c:789
#, c-format
msgid "Option \"%s\" is ignored for %s\n"
msgstr ""
-#: builtin/fetch.c:879
+#: builtin/fetch.c:888
#, c-format
msgid "Fetching %s\n"
msgstr "Baixando %s\n"
-#: builtin/fetch.c:881
+#: builtin/fetch.c:890
+#: builtin/remote.c:100
#, c-format
msgid "Could not fetch %s"
msgstr ""
-#: builtin/fetch.c:898
+#: builtin/fetch.c:907
msgid ""
"No remote repository specified. Please, specify either a URL or a\n"
"remote name from which new revisions should be fetched."
"Nenhum repositório remoto especificado. Por favor, especifique um URL ou o\n"
"nome remoto a partir do qual novas revisões devem ser obtida."
-#: builtin/fetch.c:918
+#: builtin/fetch.c:927
msgid "You need to specify a tag name."
msgstr "Você precisa especificar um nome da etiqueta."
-#: builtin/fetch.c:970
+#: builtin/fetch.c:979
msgid "fetch --all does not take a repository argument"
msgstr ""
-#: builtin/fetch.c:972
+#: builtin/fetch.c:981
msgid "fetch --all does not make sense with refspecs"
msgstr ""
-#: builtin/fetch.c:983
+#: builtin/fetch.c:992
#, c-format
msgid "No such remote or remote group: %s"
msgstr ""
-#: builtin/fetch.c:991
+#: builtin/fetch.c:1000
msgid "Fetching a group and specifying refspecs does not make sense"
msgstr ""
msgid "Invalid %s: '%s'"
msgstr "Inválido %s: '%s'"
-#: builtin/gc.c:78
-msgid "Too many options specified"
-msgstr "Demasiadas opções especificadas"
-
-#: builtin/gc.c:103
+#: builtin/gc.c:90
#, c-format
msgid "insanely long object directory %.*s"
msgstr ""
-#: builtin/gc.c:223
+#: builtin/gc.c:221
#, c-format
msgid "Auto packing the repository for optimum performance.\n"
msgstr ""
-#: builtin/gc.c:226
+#: builtin/gc.c:224
#, c-format
msgid ""
"Auto packing the repository for optimum performance. You may also\n"
"run \"git gc\" manually. See \"git help gc\" for more information.\n"
msgstr ""
-#: builtin/gc.c:256
+#: builtin/gc.c:251
msgid "There are too many unreachable loose objects; run 'git prune' to remove them."
msgstr ""
msgid "both --cached and trees are given."
msgstr ""
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "formato ajuda não reconhecido '%s'"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr ""
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr ""
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr ""
+
+#: builtin/help.c:126
+#: builtin/help.c:154
+#: builtin/help.c:163
+#: builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr ""
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr ""
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr ""
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr ""
+
+#: builtin/help.c:432
+#: builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr ""
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr ""
+
+#: builtin/index-pack.c:84
+#, c-format
+msgid "object type mismatch at %s"
+msgstr ""
+
+#: builtin/index-pack.c:104
+msgid "object of unexpected type"
+msgstr ""
+
+#: builtin/index-pack.c:141
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:151
+msgid "early EOF"
+msgstr ""
+
+#: builtin/index-pack.c:152
+msgid "read error on input"
+msgstr ""
+
+#: builtin/index-pack.c:164
+msgid "used more bytes than were available"
+msgstr ""
+
+#: builtin/index-pack.c:171
+msgid "pack too large for current definition of off_t"
+msgstr ""
+
+#: builtin/index-pack.c:187
+#, c-format
+msgid "unable to create '%s'"
+msgstr "não é possivel crear '%s'"
+
+#: builtin/index-pack.c:192
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "Não é possivel abrir o ficheiro packfile '%s'"
+
+#: builtin/index-pack.c:206
+msgid "pack signature mismatch"
+msgstr ""
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr ""
+
+#: builtin/index-pack.c:300
+#, c-format
+msgid "inflate returned %d"
+msgstr ""
+
+#: builtin/index-pack.c:345
+msgid "offset value overflow for delta base object"
+msgstr ""
+
+#: builtin/index-pack.c:353
+msgid "delta base offset is out of bound"
+msgstr ""
+
+#: builtin/index-pack.c:361
+#, c-format
+msgid "unknown object type %d"
+msgstr "ojecto com tipo desconhecido %d"
+
+#: builtin/index-pack.c:390
+msgid "cannot pread pack file"
+msgstr "Não é possivel pread pack file"
+
+#: builtin/index-pack.c:392
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:405
+msgid "serious inflate inconsistency"
+msgstr ""
+
+#: builtin/index-pack.c:476
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "não foi possível ler objecto existente %s"
+
+#: builtin/index-pack.c:479
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr ""
+
+#: builtin/index-pack.c:488
+#, c-format
+msgid "invalid blob object %s"
+msgstr "inválido objecto blob %s"
+
+#: builtin/index-pack.c:500
+#, c-format
+msgid "invalid %s"
+msgstr "inválido: %s"
+
+#: builtin/index-pack.c:502
+msgid "Error in object"
+msgstr ""
+
+#: builtin/index-pack.c:504
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr ""
+
+#: builtin/index-pack.c:576
+#: builtin/index-pack.c:602
+msgid "failed to apply delta"
+msgstr ""
+
+#: builtin/index-pack.c:706
+msgid "Receiving objects"
+msgstr ""
+
+#: builtin/index-pack.c:706
+msgid "Indexing objects"
+msgstr ""
+
+#: builtin/index-pack.c:728
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr ""
+
+#: builtin/index-pack.c:733
+msgid "cannot fstat packfile"
+msgstr "Não é possivel fstat packfile"
+
+#: builtin/index-pack.c:736
+msgid "pack has junk at the end"
+msgstr ""
+
+#: builtin/index-pack.c:754
+msgid "Resolving deltas"
+msgstr "Resolvendo deltas"
+
+#: builtin/index-pack.c:787
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr ""
+
+#: builtin/index-pack.c:866
+#, c-format
+msgid "local object %s is corrupt"
+msgstr ""
+
+#: builtin/index-pack.c:890
+msgid "error while closing pack file"
+msgstr ""
+
+#: builtin/index-pack.c:903
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "não é possivel escrever o fichero kepp '%s'"
+
+#: builtin/index-pack.c:911
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "Não é possivel fechar o fichero escrito '%s'"
+
+#: builtin/index-pack.c:924
+msgid "cannot store pack file"
+msgstr "Não é possivel guardar o fichero pack"
+
+#: builtin/index-pack.c:935
+msgid "cannot store index file"
+msgstr "Não é possivel guardar fichero index"
+
+#: builtin/index-pack.c:1024
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Não é possivel abrir o existente ficheiro pack %s"
+
+#: builtin/index-pack.c:1026
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "Não é possivel abrir o ficheiro 'pack idx' para '%s'"
+
+#: builtin/index-pack.c:1073
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1080
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1107
+msgid "Cannot come back to cwd"
+msgstr ""
+
+#: builtin/index-pack.c:1140
+#: builtin/index-pack.c:1143
+#: builtin/index-pack.c:1155
+#: builtin/index-pack.c:1159
+#, c-format
+msgid "bad %s"
+msgstr "inválido %s"
+
+#: builtin/index-pack.c:1173
+msgid "--fix-thin cannot be used without --stdin"
+msgstr ""
+
+#: builtin/index-pack.c:1177
+#: builtin/index-pack.c:1187
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr ""
+
+#: builtin/index-pack.c:1196
+msgid "--verify with no packfile name given"
+msgstr ""
+
+#: builtin/index-pack.c:1220
+msgid "confusion beyond insanity"
+msgstr ""
+
+#: builtin/index-pack.c:1239
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] ""
+msgstr[1] ""
+
#: builtin/init-db.c:35
#, c-format
msgid "Could not make %s writable by group"
msgid "Cannot access work tree '%s'"
msgstr ""
-#: builtin/log.c:187
+#: builtin/log.c:188
#, c-format
msgid "Final output: %d %s\n"
msgstr ""
-#: builtin/log.c:395
-#: builtin/log.c:483
+#: builtin/log.c:401
+#: builtin/log.c:489
#, c-format
msgid "Could not read object %s"
msgstr ""
-#: builtin/log.c:507
+#: builtin/log.c:513
#, c-format
msgid "Unknown type: %d"
msgstr "Tipo desconhecido: %d"
-#: builtin/log.c:596
+#: builtin/log.c:602
msgid "format.headers without value"
msgstr ""
-#: builtin/log.c:669
+#: builtin/log.c:675
msgid "name of output directory is too long"
msgstr "nome do diretório de saída é demasiado longo"
-#: builtin/log.c:680
+#: builtin/log.c:686
#, c-format
msgid "Cannot open patch file %s"
msgstr "Não é possivel abrir o ficheiro patch %s"
-#: builtin/log.c:694
+#: builtin/log.c:700
msgid "Need exactly one range."
msgstr "Necessita de exatamente um intervalo."
-#: builtin/log.c:702
+#: builtin/log.c:708
msgid "Not a range."
msgstr "Não é um intervalo."
-#: builtin/log.c:739
+#: builtin/log.c:745
msgid "Could not extract email from committer identity."
msgstr "Não foi possível extrair a identidade do committer do e-mail."
-#: builtin/log.c:785
+#: builtin/log.c:791
msgid "Cover letter needs email format"
msgstr "Carta de apresentação necessita um modelo de e-mail"
-#: builtin/log.c:879
+#: builtin/log.c:885
#, c-format
msgid "insane in-reply-to: %s"
msgstr ""
-#: builtin/log.c:952
+#: builtin/log.c:958
msgid "Two output directories?"
msgstr "Dois diretórios de saída?"
-#: builtin/log.c:1173
+#: builtin/log.c:1179
#, c-format
msgid "bogus committer info %s"
msgstr ""
-#: builtin/log.c:1218
+#: builtin/log.c:1224
msgid "-n and -k are mutually exclusive."
msgstr ""
-#: builtin/log.c:1220
-msgid "--subject-prefix and -k are mutually exclusive."
-msgstr ""
-
-#: builtin/log.c:1225
-#: builtin/shortlog.c:284
-#, c-format
-msgid "unrecognized argument: %s"
-msgstr "argumento não reconhecido: %s"
-
-#: builtin/log.c:1228
+#: builtin/log.c:1226
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1234
msgid "--name-only does not make sense"
msgstr ""
-#: builtin/log.c:1230
+#: builtin/log.c:1236
msgid "--name-status does not make sense"
msgstr ""
-#: builtin/log.c:1232
+#: builtin/log.c:1238
msgid "--check does not make sense"
msgstr ""
-#: builtin/log.c:1255
+#: builtin/log.c:1261
msgid "standard output, or directory, which one?"
msgstr "saída padrão, ou diretório, qual deles?"
-#: builtin/log.c:1257
+#: builtin/log.c:1263
#, c-format
msgid "Could not create directory '%s'"
msgstr ""
-#: builtin/log.c:1410
+#: builtin/log.c:1416
msgid "Failed to create output files"
msgstr "Falhou ao criar ficheiros de saída"
-#: builtin/log.c:1514
+#: builtin/log.c:1520
#, c-format
msgid "Could not find a tracked remote branch, please specify <upstream> manually.\n"
msgstr ""
-#: builtin/log.c:1530
-#: builtin/log.c:1532
-#: builtin/log.c:1544
+#: builtin/log.c:1536
+#: builtin/log.c:1538
+#: builtin/log.c:1550
#, c-format
msgid "Unknown commit %s"
msgstr "Commit desconhecido %s"
-#: builtin/merge.c:91
+#: builtin/merge.c:90
msgid "switch `m' requires a value"
msgstr ""
-#: builtin/merge.c:128
+#: builtin/merge.c:127
#, c-format
msgid "Could not find merge strategy '%s'.\n"
msgstr ""
-#: builtin/merge.c:129
+#: builtin/merge.c:128
#, c-format
msgid "Available strategies are:"
msgstr "As estratégias disponíveis são:"
-#: builtin/merge.c:134
+#: builtin/merge.c:133
#, c-format
msgid "Available custom strategies are:"
msgstr "Estratégias personalizadas disponíveis são:"
-#: builtin/merge.c:241
+#: builtin/merge.c:240
msgid "could not run stash."
msgstr ""
-#: builtin/merge.c:246
+#: builtin/merge.c:245
msgid "stash failed"
msgstr "falhou o stash"
-#: builtin/merge.c:251
+#: builtin/merge.c:250
#, c-format
msgid "not a valid object: %s"
msgstr ""
-#: builtin/merge.c:270
-#: builtin/merge.c:287
+#: builtin/merge.c:269
+#: builtin/merge.c:286
msgid "read-tree failed"
msgstr ""
-#: builtin/merge.c:317
+#: builtin/merge.c:316
msgid " (nothing to squash)"
msgstr " (nada para squash)"
-#: builtin/merge.c:330
+#: builtin/merge.c:329
#, c-format
msgid "Squash commit -- not updating HEAD\n"
msgstr ""
-#: builtin/merge.c:362
+#: builtin/merge.c:361
msgid "Writing SQUASH_MSG"
msgstr "Escrevendo SQUASH_MSG"
-#: builtin/merge.c:364
+#: builtin/merge.c:363
msgid "Finishing SQUASH_MSG"
msgstr "Terminando SQUASH_MSG"
msgid "failed to read the cache"
msgstr ""
-#: builtin/merge.c:696
+#: builtin/merge.c:697
msgid "Unable to write index."
msgstr ""
-#: builtin/merge.c:709
+#: builtin/merge.c:710
msgid "Not handling anything other than two heads merge."
msgstr ""
-#: builtin/merge.c:723
+#: builtin/merge.c:724
#, c-format
msgid "Unknown option for merge-recursive: -X%s"
msgstr ""
-#: builtin/merge.c:737
+#: builtin/merge.c:738
#, c-format
msgid "unable to write %s"
msgstr ""
-#: builtin/merge.c:876
+#: builtin/merge.c:877
#, c-format
msgid "Could not read from '%s'"
msgstr ""
-#: builtin/merge.c:885
+#: builtin/merge.c:886
#, c-format
msgid "Not committing merge; use 'git commit' to complete the merge.\n"
msgstr "Não commitando um merge; usa 'git commit' para completar o merge.\n"
-#: builtin/merge.c:891
+#: builtin/merge.c:892
msgid ""
"Please enter a commit message to explain why this merge is necessary,\n"
"especially if it merges an updated upstream into a topic branch.\n"
"the commit.\n"
msgstr ""
-#: builtin/merge.c:915
+#: builtin/merge.c:916
msgid "Empty commit message."
msgstr "Mensagem de commit vazia."
-#: builtin/merge.c:927
+#: builtin/merge.c:928
#, c-format
msgid "Wonderful.\n"
msgstr "Fastastico.\n"
-#: builtin/merge.c:1000
+#: builtin/merge.c:993
#, c-format
msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
msgstr ""
-#: builtin/merge.c:1016
+#: builtin/merge.c:1009
#, c-format
msgid "'%s' is not a commit"
msgstr "'%s' não é um commit"
-#: builtin/merge.c:1057
+#: builtin/merge.c:1050
msgid "No current branch."
msgstr "Nenhuma rama actual"
-#: builtin/merge.c:1059
+#: builtin/merge.c:1052
msgid "No remote for the current branch."
msgstr ""
-#: builtin/merge.c:1061
+#: builtin/merge.c:1054
msgid "No default upstream defined for the current branch."
msgstr ""
-#: builtin/merge.c:1066
+#: builtin/merge.c:1059
#, c-format
msgid "No remote tracking branch for %s from %s"
msgstr ""
-#: builtin/merge.c:1188
+#: builtin/merge.c:1146
+#: builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1214
msgid "There is no merge to abort (MERGE_HEAD missing)."
msgstr ""
-#: builtin/merge.c:1204
+#: builtin/merge.c:1230
#: git-pull.sh:31
msgid ""
"You have not concluded your merge (MERGE_HEAD exists).\n"
"Please, commit your changes before you can merge."
msgstr ""
-#: builtin/merge.c:1207
+#: builtin/merge.c:1233
#: git-pull.sh:34
msgid "You have not concluded your merge (MERGE_HEAD exists)."
msgstr ""
-#: builtin/merge.c:1211
+#: builtin/merge.c:1237
msgid ""
"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you can merge."
msgstr ""
-#: builtin/merge.c:1214
+#: builtin/merge.c:1240
msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
msgstr ""
-#: builtin/merge.c:1223
+#: builtin/merge.c:1249
msgid "You cannot combine --squash with --no-ff."
msgstr ""
-#: builtin/merge.c:1228
+#: builtin/merge.c:1254
msgid "You cannot combine --no-ff with --ff-only."
msgstr ""
-#: builtin/merge.c:1235
+#: builtin/merge.c:1261
msgid "No commit specified and merge.defaultToUpstream not set."
msgstr ""
-#: builtin/merge.c:1266
+#: builtin/merge.c:1293
msgid "Can merge only exactly one commit into empty head"
msgstr ""
-#: builtin/merge.c:1269
+#: builtin/merge.c:1296
msgid "Squash commit into empty head not supported yet"
msgstr ""
-#: builtin/merge.c:1271
+#: builtin/merge.c:1298
msgid "Non-fast-forward commit does not make sense into an empty head"
msgstr ""
-#: builtin/merge.c:1275
-#: builtin/merge.c:1319
-#, c-format
-msgid "%s - not something we can merge"
-msgstr ""
-
-#: builtin/merge.c:1385
+#: builtin/merge.c:1413
#, c-format
msgid "Updating %s..%s\n"
msgstr "Actualizando %s..%s\n"
-#: builtin/merge.c:1423
+#: builtin/merge.c:1451
#, c-format
msgid "Trying really trivial in-index merge...\n"
msgstr ""
-#: builtin/merge.c:1430
+#: builtin/merge.c:1458
#, c-format
msgid "Nope.\n"
msgstr "Não.\n"
-#: builtin/merge.c:1462
+#: builtin/merge.c:1490
msgid "Not possible to fast-forward, aborting."
msgstr ""
-#: builtin/merge.c:1485
-#: builtin/merge.c:1562
+#: builtin/merge.c:1513
+#: builtin/merge.c:1592
#, c-format
msgid "Rewinding the tree to pristine...\n"
msgstr ""
-#: builtin/merge.c:1489
+#: builtin/merge.c:1517
#, c-format
msgid "Trying merge strategy %s...\n"
msgstr ""
-#: builtin/merge.c:1553
+#: builtin/merge.c:1583
#, c-format
msgid "No merge strategy handled the merge.\n"
msgstr ""
-#: builtin/merge.c:1555
+#: builtin/merge.c:1585
#, c-format
msgid "Merge with strategy %s failed.\n"
msgstr "Fundir com a estratégia %s falhou.\n"
-#: builtin/merge.c:1564
+#: builtin/merge.c:1594
#, c-format
msgid "Using the %s to prepare resolving by hand.\n"
msgstr ""
-#: builtin/merge.c:1575
+#: builtin/merge.c:1606
#, c-format
msgid "Automatic merge went well; stopped before committing as requested\n"
msgstr ""
msgstr "Mudar de nome %s para %s\n"
#: builtin/mv.c:215
+#: builtin/remote.c:731
#, c-format
msgid "renaming '%s' failed"
msgstr "mudar de nome '%s' falhou"
msgstr ""
#: builtin/notes.c:175
-#: builtin/tag.c:343
+#: builtin/tag.c:347
#, c-format
msgid "could not create file '%s'"
msgstr ""
msgstr ""
#: builtin/notes.c:251
-#: builtin/tag.c:521
+#: builtin/tag.c:542
#, c-format
msgid "cannot read '%s'"
msgstr "não consegue ler '%s'"
#: builtin/notes.c:253
-#: builtin/tag.c:524
+#: builtin/tag.c:545
#, c-format
msgid "could not open or read '%s'"
msgstr ""
#: builtin/notes.c:766
#: builtin/notes.c:968
#: builtin/reset.c:293
-#: builtin/tag.c:537
+#: builtin/tag.c:558
#, c-format
msgid "Failed to resolve '%s' as a valid ref."
msgstr ""
msgstr ""
#: builtin/notes.c:1103
+#: builtin/remote.c:1598
#, c-format
msgid "Unknown subcommand: %s"
msgstr ""
-#: builtin/pack-objects.c:2310
+#: builtin/pack-objects.c:2315
#, c-format
msgid "unsupported index version %s"
msgstr ""
-#: builtin/pack-objects.c:2314
+#: builtin/pack-objects.c:2319
#, c-format
msgid "bad index version '%s'"
msgstr ""
-#: builtin/pack-objects.c:2322
+#: builtin/pack-objects.c:2342
#, c-format
msgid "option %s does not accept negative form"
msgstr "opção %s não aceita formato negativo"
-#: builtin/pack-objects.c:2326
+#: builtin/pack-objects.c:2346
#, c-format
msgid "unable to parse value '%s' for option %s"
msgstr ""
-#: builtin/push.c:44
+#: builtin/push.c:45
msgid "tag shorthand without <tag>"
msgstr ""
-#: builtin/push.c:63
+#: builtin/push.c:64
msgid "--delete only accepts plain target ref names"
msgstr "--delete só aceita nomes simples para o ref de destino"
-#: builtin/push.c:73
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+
+#: builtin/push.c:121
#, c-format
msgid ""
"You are not currently on a branch.\n"
" git push %s HEAD:<name-of-remote-branch>\n"
msgstr ""
-#: builtin/push.c:80
+#: builtin/push.c:128
#, c-format
msgid ""
"The current branch %s has no upstream branch.\n"
" git push --set-upstream %s %s\n"
msgstr ""
-#: builtin/push.c:88
+#: builtin/push.c:136
#, c-format
msgid "The current branch %s has multiple upstream branches, refusing to push."
msgstr ""
-#: builtin/push.c:111
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+
+#: builtin/push.c:174
msgid "You didn't specify any refspecs to push, and push.default is \"nothing\"."
msgstr ""
-#: builtin/push.c:131
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:233
#, c-format
msgid "Pushing to %s\n"
msgstr "Pushing para %s\n"
-#: builtin/push.c:135
+#: builtin/push.c:237
#, c-format
msgid "failed to push some refs to '%s'"
msgstr ""
-#: builtin/push.c:143
-#, c-format
-msgid ""
-"To prevent you from losing history, non-fast-forward updates were rejected\n"
-"Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
-"'Note about fast-forwards' section of 'git push --help' for details.\n"
-msgstr ""
-
-#: builtin/push.c:160
+#: builtin/push.c:269
#, c-format
msgid "bad repository '%s'"
msgstr "repositorio inválido '%s'"
-#: builtin/push.c:161
+#: builtin/push.c:270
msgid ""
"No configured push destination.\n"
"Either specify the URL from the command-line or configure a remote repository using\n"
" git push <name>\n"
msgstr ""
-#: builtin/push.c:176
+#: builtin/push.c:285
msgid "--all and --tags are incompatible"
msgstr "--all e --tags are são incompatíveis"
-#: builtin/push.c:177
+#: builtin/push.c:286
msgid "--all can't be combined with refspecs"
msgstr ""
-#: builtin/push.c:182
+#: builtin/push.c:291
msgid "--mirror and --tags are incompatible"
msgstr ""
-#: builtin/push.c:183
+#: builtin/push.c:292
msgid "--mirror can't be combined with refspecs"
msgstr ""
-#: builtin/push.c:188
+#: builtin/push.c:297
msgid "--all and --mirror are incompatible"
msgstr ""
-#: builtin/push.c:274
+#: builtin/push.c:385
msgid "--delete is incompatible with --all, --mirror and --tags"
msgstr ""
-#: builtin/push.c:276
+#: builtin/push.c:387
msgid "--delete doesn't make sense without any refs"
msgstr ""
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Actualizando %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "argumento mirror não conhecido: %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr ""
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr ""
+
+#: builtin/remote.c:195
+#: builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "o remoto %s já existe"
+
+#: builtin/remote.c:199
+#: builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' não é um nombe remoto valido"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Não foi possível configurar a rama master '%s'"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr ""
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr ""
+
+#: builtin/remote.c:440
+#: builtin/remote.c:448
+msgid "(matching)"
+msgstr ""
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(eliminado)"
+
+#: builtin/remote.c:595
+#: builtin/remote.c:601
+#: builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Não foi possível adicionar o '%s' para o '%s'"
+
+#: builtin/remote.c:639
+#: builtin/remote.c:792
+#: builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr ""
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Não foi possível renombrar a secção da configuração de '%s' para '%s'"
+
+#: builtin/remote.c:662
+#: builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Não foi possível remover a secção da configuração '%s'"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch respec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "Não foi possível adicionar '%s'"
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Não foi possível atribuir '%s'"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "falhou eliminar '%s'"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "falhou a criar '%s'"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Não foi possível remover rama %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr ""
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr "seguido"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr ""
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr ""
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr ""
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr "Fundir com servidor remoto %s"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " e com remoto"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "Fundir com servidor remoto %s"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " e com remoto"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "creado"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "eliminado"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "actualizado"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "fast-forwardable"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "local desatualizada"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr ""
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr ""
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* remota %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1120
+#: builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(nenhum URL)"
+
+#: builtin/remote.c:1129
+#: builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1133
+#: builtin/remote.c:1135
+#: builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr "Rama HEAD: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid " HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr ""
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] "Rama remota:%s"
+msgstr[1] "Ramas remotas:%s'"
+
+#: builtin/remote.c:1154
+#: builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr ""
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr ""
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr ""
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr ""
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Não foi possível abrir %s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr ""
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Não foi possível configurar %s"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "Apagando %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr ""
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr ""
+
+#: builtin/remote.c:1387
+#: builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Não existe este remoto '%s'"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "Nenhum remoto especificado"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr ""
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr ""
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Nenhum URL encontrado: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr ""
+
#: builtin/reset.c:33
msgid "mixed"
msgstr "mistura"
msgid "hard"
msgstr "forte"
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "juntar"
+
#: builtin/reset.c:33
msgid "keep"
msgstr "manter"
msgstr ""
#: builtin/revert.c:70
-#: builtin/revert.c:91
+#: builtin/revert.c:92
#, c-format
msgid "%s: %s cannot be used with %s"
msgstr ""
-#: builtin/revert.c:126
+#: builtin/revert.c:131
msgid "program error"
msgstr "erro do programa"
-#: builtin/revert.c:209
+#: builtin/revert.c:221
msgid "revert failed"
msgstr "falhou o revert"
-#: builtin/revert.c:224
+#: builtin/revert.c:236
msgid "cherry-pick failed"
msgstr "cherry-pick falhou"
msgid "Missing author: %s"
msgstr "Autor em falta: %s"
-#: builtin/tag.c:58
+#: builtin/tag.c:60
#, c-format
msgid "malformed object at '%s'"
msgstr ""
-#: builtin/tag.c:205
+#: builtin/tag.c:207
#, c-format
msgid "tag name too long: %.*s..."
msgstr ""
-#: builtin/tag.c:210
+#: builtin/tag.c:212
#, c-format
msgid "tag '%s' not found."
msgstr "etiqueta '%s' não foi encontrada."
-#: builtin/tag.c:225
+#: builtin/tag.c:227
#, c-format
msgid "Deleted tag '%s' (was %s)\n"
msgstr ""
-#: builtin/tag.c:237
+#: builtin/tag.c:239
#, c-format
msgid "could not verify the tag '%s'"
msgstr ""
-#: builtin/tag.c:247
+#: builtin/tag.c:249
msgid ""
"\n"
"#\n"
"#\n"
msgstr ""
-#: builtin/tag.c:254
+#: builtin/tag.c:256
msgid ""
"\n"
"#\n"
"#\n"
msgstr ""
-#: builtin/tag.c:294
+#: builtin/tag.c:298
msgid "unable to sign the tag"
msgstr ""
-#: builtin/tag.c:296
+#: builtin/tag.c:300
msgid "unable to write tag file"
msgstr ""
-#: builtin/tag.c:321
+#: builtin/tag.c:325
msgid "bad object type."
msgstr ""
-#: builtin/tag.c:334
+#: builtin/tag.c:338
msgid "tag header too big."
msgstr ""
-#: builtin/tag.c:366
+#: builtin/tag.c:370
msgid "no tag message?"
msgstr "nenhuma mensaje para a etiqueta?"
-#: builtin/tag.c:372
+#: builtin/tag.c:376
#, c-format
msgid "The tag message has been left in %s\n"
msgstr ""
-#: builtin/tag.c:421
+#: builtin/tag.c:425
msgid "switch 'points-at' requires an object"
msgstr ""
-#: builtin/tag.c:423
+#: builtin/tag.c:427
#, c-format
msgid "malformed object name '%s'"
msgstr ""
-#: builtin/tag.c:502
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column e -n are são incompatíveis"
+
+#: builtin/tag.c:523
msgid "-n option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:504
+#: builtin/tag.c:525
msgid "--contains option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:506
+#: builtin/tag.c:527
msgid "--points-at option is only allowed with -l."
msgstr ""
-#: builtin/tag.c:514
+#: builtin/tag.c:535
msgid "only one -F or -m option is allowed."
msgstr ""
-#: builtin/tag.c:534
+#: builtin/tag.c:555
msgid "too many params"
msgstr "demasiado parametros"
-#: builtin/tag.c:540
+#: builtin/tag.c:561
#, c-format
msgid "'%s' is not a valid tag name."
msgstr ""
-#: builtin/tag.c:545
+#: builtin/tag.c:566
#, c-format
msgid "tag '%s' already exists"
msgstr "etiqueta '%s' já existe"
-#: builtin/tag.c:563
+#: builtin/tag.c:584
#, c-format
msgid "%s: cannot lock the ref"
msgstr ""
-#: builtin/tag.c:565
+#: builtin/tag.c:586
#, c-format
msgid "%s: cannot update the ref"
msgstr ""
-#: builtin/tag.c:567
+#: builtin/tag.c:588
#, c-format
msgid "Updated tag '%s' (was %s)\n"
msgstr ""
-#: git-am.sh:49
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr ""
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr ""
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Listar, criar ou apagar ramas"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr ""
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr ""
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Gravar alterações para o repositório"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr ""
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr ""
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr ""
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr ""
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "Mostrado logs de commits"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr ""
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr ""
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr ""
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr ""
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr ""
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr ""
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr ""
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr ""
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "Mostrar o estado los ramos das árvores de trabalho"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr ""
+
+#: git-am.sh:50
msgid "You need to set your committer info first"
msgstr "Necessitas primeiro de especificiar os teus dados de committer"
-#: git-am.sh:136
+#: git-am.sh:137
msgid "Repository lacks necessary blobs to fall back on 3-way merge."
msgstr ""
-#: git-am.sh:147
+#: git-am.sh:154
msgid ""
"Did you hand edit your patch?\n"
"It does not apply to blobs recorded in its index."
msgstr ""
-#: git-am.sh:156
+#: git-am.sh:163
msgid "Falling back to patching base and 3-way merge..."
msgstr ""
-#: git-am.sh:268
+#: git-am.sh:275
msgid "Only one StGIT patch series can be applied at once"
msgstr ""
-#: git-am.sh:355
+#: git-am.sh:362
#, sh-format
msgid "Patch format $patch_format is not supported."
msgstr ""
-#: git-am.sh:357
+#: git-am.sh:364
msgid "Patch format detection failed."
msgstr "Falhou a detecção do formato do patch."
-#: git-am.sh:411
+#: git-am.sh:418
msgid "-d option is no longer supported. Do not use."
msgstr ""
-#: git-am.sh:474
+#: git-am.sh:481
#, sh-format
msgid "previous rebase directory $dotest still exists but mbox given."
msgstr ""
-#: git-am.sh:479
+#: git-am.sh:486
msgid "Please make up your mind. --skip or --abort?"
msgstr ""
-#: git-am.sh:506
+#: git-am.sh:513
msgid "Resolve operation not in progress, we are not resuming."
msgstr ""
-#: git-am.sh:572
+#: git-am.sh:579
#, sh-format
msgid "Dirty index: cannot apply patches (dirty: $files)"
msgstr ""
-#: git-am.sh:748
+#: git-am.sh:755
msgid "cannot be interactive without stdin connected to a terminal."
msgstr ""
#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
#. in your translation. The program will only accept English
#. input at this point.
-#: git-am.sh:759
+#: git-am.sh:766
msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
msgstr "Aplicar? Sim[y]/[n]ão/[e]ditar/[v]er patch/[a]ceitar todos "
-#: git-am.sh:795
+#: git-am.sh:802
#, sh-format
msgid "Applying: $FIRSTLINE"
msgstr "Aplicando: $FIRSTLINE"
-#: git-am.sh:840
+#: git-am.sh:847
msgid "No changes -- Patch already applied."
msgstr "Nenhuma mudança -- Já foi aplicado o patch."
-#: git-am.sh:866
+#: git-am.sh:873
msgid "applying to an empty history"
msgstr ""
#: git-bisect.sh:474
msgid "We are not bisecting."
-msgstr ""
+msgstr "Não estamos a bisseccionar."
#: git-pull.sh:21
msgid ""
msgid "cannot strip one component off url '$remoteurl'"
msgstr ""
-#: git-submodule.sh:108
+#: git-submodule.sh:109
#, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
msgstr ""
-#: git-submodule.sh:149
+#: git-submodule.sh:150
#, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
msgstr ""
-#: git-submodule.sh:159
+#: git-submodule.sh:160
#, sh-format
msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
msgstr ""
-#: git-submodule.sh:247
+#: git-submodule.sh:249
#, sh-format
msgid "repo URL: '$repo' must be absolute or begin with ./|../"
msgstr ""
-#: git-submodule.sh:264
+#: git-submodule.sh:266
#, sh-format
-msgid "'$path' already exists in the index"
+msgid "'$sm_path' already exists in the index"
msgstr ""
-#: git-submodule.sh:281
+#: git-submodule.sh:283
#, sh-format
-msgid "'$path' already exists and is not a valid git repo"
+msgid "'$sm_path' already exists and is not a valid git repo"
msgstr ""
-#: git-submodule.sh:295
+#: git-submodule.sh:297
#, sh-format
-msgid "Unable to checkout submodule '$path'"
+msgid "Unable to checkout submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:300
+#: git-submodule.sh:302
#, sh-format
-msgid "Failed to add submodule '$path'"
+msgid "Failed to add submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:305
+#: git-submodule.sh:307
#, sh-format
-msgid "Failed to register submodule '$path'"
+msgid "Failed to register submodule '$sm_path'"
msgstr ""
-#: git-submodule.sh:347
+#: git-submodule.sh:349
#, sh-format
-msgid "Entering '$prefix$path'"
-msgstr "Entrando '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
+msgstr "Entrando '$prefix$sm_path'"
-#: git-submodule.sh:359
+#: git-submodule.sh:363
#, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
+msgid "Stopping at '$sm_path'; script returned non-zero status."
msgstr ""
-#: git-submodule.sh:401
+#: git-submodule.sh:405
#, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
msgstr ""
-#: git-submodule.sh:410
+#: git-submodule.sh:414
#, sh-format
-msgid "Failed to register url for submodule path '$path'"
+msgid "Failed to register url for submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:418
+#: git-submodule.sh:422
#, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
+msgid "Failed to register update mode for submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:420
+#: git-submodule.sh:424
#, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
msgstr ""
-#: git-submodule.sh:519
+#: git-submodule.sh:523
#, sh-format
msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
"Maybe you want to use 'update --init'?"
msgstr ""
-#: git-submodule.sh:532
+#: git-submodule.sh:536
#, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
+msgid "Unable to find current revision in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:551
+#: git-submodule.sh:555
#, sh-format
-msgid "Unable to fetch in submodule path '$path'"
+msgid "Unable to fetch in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:565
+#: git-submodule.sh:569
#, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:566
+#: git-submodule.sh:570
#, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
msgstr ""
-#: git-submodule.sh:571
+#: git-submodule.sh:575
#, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:572
+#: git-submodule.sh:576
#, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
msgstr ""
-#: git-submodule.sh:577
+#: git-submodule.sh:581
#, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:578
+#: git-submodule.sh:582
#, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
msgstr ""
-#: git-submodule.sh:600
-#: git-submodule.sh:923
+#: git-submodule.sh:604
+#: git-submodule.sh:927
#, sh-format
-msgid "Failed to recurse into submodule path '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
msgstr ""
-#: git-submodule.sh:708
+#: git-submodule.sh:712
msgid "--"
msgstr "--"
-#: git-submodule.sh:766
+#: git-submodule.sh:770
#, sh-format
msgid " Warn: $name doesn't contain commit $sha1_src"
msgstr ""
-#: git-submodule.sh:769
+#: git-submodule.sh:773
#, sh-format
msgid " Warn: $name doesn't contain commit $sha1_dst"
msgstr ""
-#: git-submodule.sh:772
+#: git-submodule.sh:776
#, sh-format
msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
msgstr ""
-#: git-submodule.sh:797
+#: git-submodule.sh:801
msgid "blob"
msgstr "blob"
-#: git-submodule.sh:798
+#: git-submodule.sh:802
msgid "submodule"
msgstr "submódulos"
-#: git-submodule.sh:969
+#: git-submodule.sh:973
#, sh-format
msgid "Synchronizing submodule url for '$name'"
msgstr ""
+#~ msgid "cherry-pick"
+#~ msgstr "cherry-pick"
+
+#~ msgid "Please enter the commit message for your changes."
+#~ msgstr "Por favor insira a mensagem de commit das suas alterações."
+
+#~ msgid "Too many options specified"
+#~ msgstr "Demasiadas opções especificadas"
"'git add/rm <file>' 标记解决方案,\n"
"或使用 'git commit -a'。"
-#: commit.c:47
+#: commit.c:48
#, c-format
msgid "could not parse %s"
msgstr "不能解析 %s"
-#: commit.c:49
+#: commit.c:50
#, c-format
msgid "%s %s is not a commit!"
msgstr "%s %s 不是一个提交!"
msgid "could not apply %s... %s"
msgstr "不能应用 %s... %s"
-#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
-#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:347
+#: sequencer.c:450 sequencer.c:909 builtin/log.c:289 builtin/log.c:719
+#: builtin/log.c:1335 builtin/log.c:1554 builtin/merge.c:347
#: builtin/shortlog.c:181
msgid "revision walk setup failed"
msgstr "版本遍历设置失败"
msgid "Can't cherry-pick into empty head"
msgstr "不能拣选到空分支"
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD 没有指向一个分支"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "没有此分支:'%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "尚未给分支 '%s' 设置上游"
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "上游分支 '%s' 没有存储为一个远程跟踪分支"
+
#: wt-status.c:134
msgid "Unmerged paths:"
msgstr "未合并的路径:"
# 译者:汉字之间无空格,故删除尾部空格
#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
+#: builtin/branch.c:164
msgid "remote "
msgstr "远程"
-#: builtin/branch.c:171
+#: builtin/branch.c:172
msgid "cannot use -a with -d"
msgstr "不能将 -a 和 -d 共用"
-#: builtin/branch.c:177
+#: builtin/branch.c:178
msgid "Couldn't look up commit object for HEAD"
msgstr "无法查询 HEAD 指向的提交对象"
-#: builtin/branch.c:182
+#: builtin/branch.c:183
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr "无法删除您当前所在的分支 '%s'。"
-#: builtin/branch.c:192
+#: builtin/branch.c:193
#, c-format
msgid "%sbranch '%s' not found."
msgstr "%s分支 '%s' 未发现。"
-#: builtin/branch.c:200
+#: builtin/branch.c:201
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "无法查询 '%s' 指向的提交对象"
-#: builtin/branch.c:206
+#: builtin/branch.c:207
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"分支 '%s' 没有完全合并。\n"
"如果您确认要删除它,执行 'git branch -D %s'。"
-#: builtin/branch.c:214
+#: builtin/branch.c:215
#, c-format
msgid "Error deleting %sbranch '%s'"
msgstr "删除 %s分支 '%s' 时出错"
-#: builtin/branch.c:219
+#: builtin/branch.c:221
#, c-format
msgid "Deleted %sbranch %s (was %s).\n"
msgstr "已删除 %s分支 %s(曾为 %s)。\n"
-#: builtin/branch.c:224
+#: builtin/branch.c:226
msgid "Update of config-file failed"
msgstr "无法更新 config 文件"
-#: builtin/branch.c:322
+#: builtin/branch.c:324
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "分支 '%s' 未指向一个提交"
# 译者:注意保持句尾空格
-#: builtin/branch.c:394
+#: builtin/branch.c:396
#, c-format
msgid "behind %d] "
msgstr "落后 %d] "
# 译者:注意保持句尾空格
-#: builtin/branch.c:396
+#: builtin/branch.c:398
#, c-format
msgid "ahead %d] "
msgstr "领先 %d] "
# 译者:注意保持句尾空格
-#: builtin/branch.c:398
+#: builtin/branch.c:400
#, c-format
msgid "ahead %d, behind %d] "
msgstr "领先 %d,落后 %d] "
-#: builtin/branch.c:501
+#: builtin/branch.c:503
msgid "(no branch)"
msgstr "(非分支)"
-#: builtin/branch.c:566
+#: builtin/branch.c:568
msgid "some refs could not be read"
msgstr "一些引用不能读取"
-#: builtin/branch.c:579
+#: builtin/branch.c:581
msgid "cannot rename the current branch while not on any."
msgstr "无法重命名当前分支因为不处于任何分支上。"
-#: builtin/branch.c:589
+#: builtin/branch.c:591
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "无效的分支名:'%s'"
-#: builtin/branch.c:604
+#: builtin/branch.c:606
msgid "Branch rename failed"
msgstr "分支重命名失败"
-#: builtin/branch.c:608
+#: builtin/branch.c:610
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "重命名掉一个错误命名的旧分支 '%s'"
-#: builtin/branch.c:612
+#: builtin/branch.c:614
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "分支重命名为 %s,但 HEAD 没有更新!"
-#: builtin/branch.c:619
+#: builtin/branch.c:621
msgid "Branch is renamed, but update of config-file failed"
msgstr "分支被重命名,但更新 config 文件失败"
-#: builtin/branch.c:634
+#: builtin/branch.c:636
#, c-format
msgid "malformed object name %s"
msgstr "非法的对象名 %s"
-#: builtin/branch.c:658
+#: builtin/branch.c:660
#, c-format
msgid "could not write branch description template: %s\n"
msgstr "不能写分支描述模版:%s\n"
-#: builtin/branch.c:746
+#: builtin/branch.c:750
msgid "Failed to resolve HEAD as a valid ref."
msgstr "无法将 HEAD 解析为有效引用。"
-#: builtin/branch.c:751 builtin/clone.c:558
+#: builtin/branch.c:755 builtin/clone.c:558
msgid "HEAD not found below refs/heads!"
msgstr "HEAD 没有位于 /refs/heads 之下!"
-#: builtin/branch.c:809
+#: builtin/branch.c:813
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr "'git branch' 的 -a 和 -r 选项带一个分支名参数没有意义"
msgid "Can not do reflog for '%s'\n"
msgstr "不能对 '%s' 执行 reflog 操作\n"
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
msgid "HEAD is now at"
msgstr "HEAD 目前位于"
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
#, c-format
msgid "Reset branch '%s'\n"
msgstr "重置分支 '%s'\n"
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
#, c-format
msgid "Already on '%s'\n"
msgstr "已经位于 '%s'\n"
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "切换并重置分支 '%s'\n"
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "切换到一个新分支 '%s'\n"
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "切换到分支 '%s'\n"
# 译者:注意保持前导空格
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
#, c-format
msgid " ... and %d more.\n"
msgstr " ... 及其它 %d 个。\n"
#. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
msgid "Cannot access work tree '%s'"
msgstr "不能访问工作区 '%s'"
-#: builtin/log.c:187
+#: builtin/log.c:188
#, c-format
msgid "Final output: %d %s\n"
msgstr "最终输出:%d %s\n"
-#: builtin/log.c:395 builtin/log.c:483
+#: builtin/log.c:401 builtin/log.c:489
#, c-format
msgid "Could not read object %s"
msgstr "不能读取对象 %s"
-#: builtin/log.c:507
+#: builtin/log.c:513
#, c-format
msgid "Unknown type: %d"
msgstr "未知类型:%d"
-#: builtin/log.c:596
+#: builtin/log.c:602
msgid "format.headers without value"
msgstr "format.headers 没有值"
-#: builtin/log.c:669
+#: builtin/log.c:675
msgid "name of output directory is too long"
msgstr "输出目录名太长"
-#: builtin/log.c:680
+#: builtin/log.c:686
#, c-format
msgid "Cannot open patch file %s"
msgstr "无法打开补丁文件 %s"
-#: builtin/log.c:694
+#: builtin/log.c:700
msgid "Need exactly one range."
msgstr "只需要一个范围。"
-#: builtin/log.c:702
+#: builtin/log.c:708
msgid "Not a range."
msgstr "不是一个范围。"
-#: builtin/log.c:739
+#: builtin/log.c:745
msgid "Could not extract email from committer identity."
msgstr "不能从提交者身份中提取邮件地址。"
-#: builtin/log.c:785
+#: builtin/log.c:791
msgid "Cover letter needs email format"
msgstr "信封需要邮件地址格式"
-#: builtin/log.c:879
+#: builtin/log.c:885
#, c-format
msgid "insane in-reply-to: %s"
msgstr "不正常的 in-reply-to:%s"
-#: builtin/log.c:952
+#: builtin/log.c:958
msgid "Two output directories?"
msgstr "两个输出目录?"
-#: builtin/log.c:1173
+#: builtin/log.c:1179
#, c-format
msgid "bogus committer info %s"
msgstr "虚假的提交者信息 %s"
-#: builtin/log.c:1218
+#: builtin/log.c:1224
msgid "-n and -k are mutually exclusive."
msgstr "-n 和 -k 互斥。"
-#: builtin/log.c:1220
+#: builtin/log.c:1226
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "--subject-prefix 和 -k 互斥。"
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/log.c:1231 builtin/shortlog.c:284
#, c-format
msgid "unrecognized argument: %s"
msgstr "未识别的参数:%s"
-#: builtin/log.c:1228
+#: builtin/log.c:1234
msgid "--name-only does not make sense"
msgstr "--name-only 无意义"
-#: builtin/log.c:1230
+#: builtin/log.c:1236
msgid "--name-status does not make sense"
msgstr "--name-status 无意义"
-#: builtin/log.c:1232
+#: builtin/log.c:1238
msgid "--check does not make sense"
msgstr "--check 无意义"
-#: builtin/log.c:1255
+#: builtin/log.c:1261
msgid "standard output, or directory, which one?"
msgstr "标准输出或目录,哪一个?"
-#: builtin/log.c:1257
+#: builtin/log.c:1263
#, c-format
msgid "Could not create directory '%s'"
msgstr "不能创建目录 '%s'"
-#: builtin/log.c:1410
+#: builtin/log.c:1416
msgid "Failed to create output files"
msgstr "无法创建输出文件"
-#: builtin/log.c:1514
+#: builtin/log.c:1520
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
msgstr "不能找到跟踪的远程分支,请手工指定 <upstream>。\n"
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
#, c-format
msgid "Unknown commit %s"
msgstr "未知提交 %s"
msgid "Updated tag '%s' (was %s)\n"
msgstr "已更新tag '%s'(曾为 %s)\n"
-#: git-am.sh:49
+#: git-am.sh:50
msgid "You need to set your committer info first"
msgstr "您需要先设置你的提交者信息"
-#: git-am.sh:136
+#: git-am.sh:137
msgid "Repository lacks necessary blobs to fall back on 3-way merge."
msgstr "版本库缺乏必要的 blob 数据以进行三路合并。"
-#: git-am.sh:147
+#: git-am.sh:154
msgid ""
"Did you hand edit your patch?\n"
"It does not apply to blobs recorded in its index."
"您是否曾手动编辑过您的补丁?\n"
"无法应用补丁到索引中的数据上。"
-#: git-am.sh:156
+#: git-am.sh:163
msgid "Falling back to patching base and 3-way merge..."
msgstr "回退到补丁基础版本并使用三路合并..."
-#: git-am.sh:268
+#: git-am.sh:275
msgid "Only one StGIT patch series can be applied at once"
msgstr "一次只能有一个 StGIT 补丁队列被应用"
-#: git-am.sh:355
+#: git-am.sh:362
#, sh-format
msgid "Patch format $patch_format is not supported."
msgstr "不支持 $patch_format 补丁格式。"
-#: git-am.sh:357
+#: git-am.sh:364
msgid "Patch format detection failed."
msgstr "补丁格式检测失败。"
-#: git-am.sh:411
+#: git-am.sh:418
msgid "-d option is no longer supported. Do not use."
msgstr "不再支持 -d 选项。不要使用。"
-#: git-am.sh:474
+#: git-am.sh:481
#, sh-format
msgid "previous rebase directory $dotest still exists but mbox given."
msgstr "之前的变基目录 $dotest 仍然存在但给出了mbox。"
-#: git-am.sh:479
+#: git-am.sh:486
msgid "Please make up your mind. --skip or --abort?"
msgstr "请下决心。--skip 或是 --abort ?"
-#: git-am.sh:506
+#: git-am.sh:513
msgid "Resolve operation not in progress, we are not resuming."
msgstr "解决操作未进行,我们不会继续。"
-#: git-am.sh:572
+#: git-am.sh:579
#, sh-format
msgid "Dirty index: cannot apply patches (dirty: $files)"
msgstr "脏的索引:不能应用补丁(脏文件:$files)"
-#: git-am.sh:748
+#: git-am.sh:755
msgid "cannot be interactive without stdin connected to a terminal."
msgstr "标准输入没有和终端关联,不能进行交互式操作。"
#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
#. in your translation. The program will only accept English
#. input at this point.
-#: git-am.sh:759
+#: git-am.sh:766
msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
msgstr "应用?[y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-#: git-am.sh:795
+#: git-am.sh:802
#, sh-format
msgid "Applying: $FIRSTLINE"
msgstr "正应用:$FIRSTLINE"
-#: git-am.sh:840
+#: git-am.sh:847
msgid "No changes -- Patch already applied."
msgstr "没有变更 -- 补丁已经应用过。"
-#: git-am.sh:866
+#: git-am.sh:873
msgid "applying to an empty history"
msgstr "正应用到一个空历史上"
msgid "cannot strip one component off url '$remoteurl'"
msgstr "无法从 url '$remoteurl' 剥离一个组件"
-#: git-submodule.sh:108
+#: git-submodule.sh:109
#, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
-msgstr "未在 .gitmodules 中发现路径 '$path' 的子模组映射"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "未在 .gitmodules 中发现路径 '$sm_path' 的子模组映射"
-#: git-submodule.sh:149
+#: git-submodule.sh:150
#, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
-msgstr "无法克隆 '$url' 到子模组路径 '$path'"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "无法克隆 '$url' 到子模组路径 '$sm_path'"
-#: git-submodule.sh:159
+#: git-submodule.sh:160
#, sh-format
msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
msgstr "Gitdir '$a' 在子模组路径 '$b' 之下或者相反"
#: git-submodule.sh:265
#, sh-format
-msgid "'$path' already exists in the index"
-msgstr "'$path' 已经存在于索引中"
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' 已经存在于索引中"
#: git-submodule.sh:282
#, sh-format
-msgid "'$path' already exists and is not a valid git repo"
-msgstr "'$path' 已存在且不是一个有效的 git 版本库"
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' 已存在且不是一个有效的 git 版本库"
#: git-submodule.sh:296
#, sh-format
-msgid "Unable to checkout submodule '$path'"
-msgstr "不能检出子模组 '$path'"
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "不能检出子模组 '$sm_path'"
#: git-submodule.sh:301
#, sh-format
-msgid "Failed to add submodule '$path'"
-msgstr "无法添加子模组 '$path'"
+msgid "Failed to add submodule '$sm_path'"
+msgstr "无法添加子模组 '$sm_path'"
#: git-submodule.sh:306
#, sh-format
-msgid "Failed to register submodule '$path'"
-msgstr "无法注册子模组 '$path'"
+msgid "Failed to register submodule '$sm_path'"
+msgstr "无法注册子模组 '$sm_path'"
#: git-submodule.sh:348
#, sh-format
-msgid "Entering '$prefix$path'"
-msgstr "正在进入 '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
+msgstr "正在进入 '$prefix$sm_path'"
#: git-submodule.sh:360
#, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
-msgstr "停止于 '$path',脚本返回非零值。"
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "停止于 '$sm_path',脚本返回非零值。"
#: git-submodule.sh:402
#, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
-msgstr "在 .gitmodules 中未找到子模组路径 '$path' 的 url"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "在 .gitmodules 中未找到子模组路径 '$sm_path' 的 url"
#: git-submodule.sh:411
#, sh-format
-msgid "Failed to register url for submodule path '$path'"
-msgstr "无法为子模组路径 '$path' 注册 url"
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "无法为子模组路径 '$sm_path' 注册 url"
#: git-submodule.sh:419
#, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
-msgstr "无法为子模组路径 '$path' 注册更新模式"
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr "无法为子模组路径 '$sm_path' 注册更新模式"
#: git-submodule.sh:421
#, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
-msgstr "子模组 '$name' ($url) 已为路径 '$path' 注册"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "子模组 '$name' ($url) 已为路径 '$sm_path' 注册"
#: git-submodule.sh:520
#, sh-format
msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
"Maybe you want to use 'update --init'?"
msgstr ""
-"子模组路径 '$path' 没有初始化\n"
+"子模组路径 '$sm_path' 没有初始化\n"
"也许您想用 'update --init'?"
#: git-submodule.sh:533
#, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中找到当前版本"
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中找到当前版本"
#: git-submodule.sh:552
#, sh-format
-msgid "Unable to fetch in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中获取"
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中获取"
#: git-submodule.sh:566
#, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中变基 '$sha1'"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中变基 '$sha1'"
#: git-submodule.sh:567
#, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
-msgstr "子模组路径 '$path':变基至 '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "子模组路径 '$sm_path':变基至 '$sha1'"
#: git-submodule.sh:572
#, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
-msgstr "无法合并 '$sha1' 到子模组路径 '$path' 中"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "无法合并 '$sha1' 到子模组路径 '$sm_path' 中"
#: git-submodule.sh:573
#, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
-msgstr "子模组路径 '$path':已合并入 '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "子模组路径 '$sm_path':已合并入 '$sha1'"
#: git-submodule.sh:578
#, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中检出 '$sha1'"
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中检出 '$sha1'"
#: git-submodule.sh:579
#, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
-msgstr "子模组路径 '$path':检出 '$sha1'"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "子模组路径 '$sm_path':检出 '$sha1'"
#: git-submodule.sh:601 git-submodule.sh:924
#, sh-format
-msgid "Failed to recurse into submodule path '$path'"
-msgstr "无法递归进子模组路径 '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "无法递归进子模组路径 '$sm_path'"
#: git-submodule.sh:709
msgid "--"
#include "commit.h"
#include "blob.h"
#include "resolve-undo.h"
+#include "strbuf.h"
+#include "varint.h"
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
return refresh_cache_ent(&the_index, ce, really, NULL, NULL);
}
+
+/*****************************************************************
+ * Index File I/O
+ *****************************************************************/
+
+#define INDEX_FORMAT_DEFAULT 3
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * We save the fields in big-endian order to allow using the
+ * index file over NFS transparently.
+ */
+struct ondisk_cache_entry {
+ struct cache_time ctime;
+ struct cache_time mtime;
+ unsigned int dev;
+ unsigned int ino;
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ unsigned char sha1[20];
+ unsigned short flags;
+ char name[FLEX_ARRAY]; /* more */
+};
+
+/*
+ * This struct is used when CE_EXTENDED bit is 1
+ * The struct must match ondisk_cache_entry exactly from
+ * ctime till flags
+ */
+struct ondisk_cache_entry_extended {
+ struct cache_time ctime;
+ struct cache_time mtime;
+ unsigned int dev;
+ unsigned int ino;
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ unsigned char sha1[20];
+ unsigned short flags;
+ unsigned short flags2;
+ char name[FLEX_ARRAY]; /* more */
+};
+
+/* These are only used for v3 or lower */
+#define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
+#define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
+#define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
+#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
+ ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
+ ondisk_cache_entry_size(ce_namelen(ce)))
+
static int verify_hdr(struct cache_header *hdr, unsigned long size)
{
git_SHA_CTX c;
unsigned char sha1[20];
+ int hdr_version;
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
return error("bad signature");
- if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
- return error("bad index version");
+ hdr_version = ntohl(hdr->hdr_version);
+ if (hdr_version < 2 || 4 < hdr_version)
+ return error("bad index version %d", hdr_version);
git_SHA1_Init(&c);
git_SHA1_Update(&c, hdr, size - 20);
git_SHA1_Final(sha1, &c);
return read_index_from(istate, get_index_file());
}
-static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk)
+#ifndef NEEDS_ALIGNED_ACCESS
+#define ntoh_s(var) ntohs(var)
+#define ntoh_l(var) ntohl(var)
+#else
+static inline uint16_t ntoh_s_force_align(void *p)
+{
+ uint16_t x;
+ memcpy(&x, p, sizeof(x));
+ return ntohs(x);
+}
+static inline uint32_t ntoh_l_force_align(void *p)
+{
+ uint32_t x;
+ memcpy(&x, p, sizeof(x));
+ return ntohl(x);
+}
+#define ntoh_s(var) ntoh_s_force_align(&(var))
+#define ntoh_l(var) ntoh_l_force_align(&(var))
+#endif
+
+static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
+ unsigned int flags,
+ const char *name,
+ size_t len)
+{
+ struct cache_entry *ce = xmalloc(cache_entry_size(len));
+
+ ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
+ ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
+ ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+ ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+ ce->ce_dev = ntoh_l(ondisk->dev);
+ ce->ce_ino = ntoh_l(ondisk->ino);
+ ce->ce_mode = ntoh_l(ondisk->mode);
+ ce->ce_uid = ntoh_l(ondisk->uid);
+ ce->ce_gid = ntoh_l(ondisk->gid);
+ ce->ce_size = ntoh_l(ondisk->size);
+ ce->ce_flags = flags;
+ hashcpy(ce->sha1, ondisk->sha1);
+ memcpy(ce->name, name, len);
+ ce->name[len] = '\0';
+ return ce;
+}
+
+/*
+ * Adjacent cache entries tend to share the leading paths, so it makes
+ * sense to only store the differences in later entries. In the v4
+ * on-disk format of the index, each on-disk cache entry stores the
+ * number of bytes to be stripped from the end of the previous name,
+ * and the bytes to append to the result, to come up with its name.
+ */
+static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
+{
+ const unsigned char *ep, *cp = (const unsigned char *)cp_;
+ size_t len = decode_varint(&cp);
+
+ if (name->len < len)
+ die("malformed name field in the index");
+ strbuf_remove(name, name->len - len, len);
+ for (ep = cp; *ep; ep++)
+ ; /* find the end */
+ strbuf_add(name, cp, ep - cp);
+ return (const char *)ep + 1 - cp_;
+}
+
+static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
+ unsigned long *ent_size,
+ struct strbuf *previous_name)
{
struct cache_entry *ce;
size_t len;
unsigned int flags;
/* On-disk flags are just 16 bits */
- flags = ntohs(ondisk->flags);
+ flags = ntoh_s(ondisk->flags);
len = flags & CE_NAMEMASK;
if (flags & CE_EXTENDED) {
struct ondisk_cache_entry_extended *ondisk2;
int extended_flags;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
- extended_flags = ntohs(ondisk2->flags2) << 16;
+ extended_flags = ntoh_s(ondisk2->flags2) << 16;
/* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
if (extended_flags & ~CE_EXTENDED_FLAGS)
die("Unknown index entry format %08x", extended_flags);
else
name = ondisk->name;
- if (len == CE_NAMEMASK)
- len = strlen(name);
-
- ce = xmalloc(cache_entry_size(len));
-
- ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
- ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
- ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
- ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
- ce->ce_dev = ntohl(ondisk->dev);
- ce->ce_ino = ntohl(ondisk->ino);
- ce->ce_mode = ntohl(ondisk->mode);
- ce->ce_uid = ntohl(ondisk->uid);
- ce->ce_gid = ntohl(ondisk->gid);
- ce->ce_size = ntohl(ondisk->size);
- ce->ce_flags = flags;
-
- hashcpy(ce->sha1, ondisk->sha1);
-
- memcpy(ce->name, name, len);
- ce->name[len] = '\0';
+ if (!previous_name) {
+ /* v3 and earlier */
+ if (len == CE_NAMEMASK)
+ len = strlen(name);
+ ce = cache_entry_from_ondisk(ondisk, flags, name, len);
+
+ *ent_size = ondisk_ce_size(ce);
+ } else {
+ unsigned long consumed;
+ consumed = expand_name_field(previous_name, name);
+ ce = cache_entry_from_ondisk(ondisk, flags,
+ previous_name->buf,
+ previous_name->len);
+
+ *ent_size = (name - ((char *)ondisk)) + consumed;
+ }
return ce;
}
struct cache_header *hdr;
void *mmap;
size_t mmap_size;
+ struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
errno = EBUSY;
if (istate->initialized)
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
+ istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
istate->initialized = 1;
+ if (istate->version == 4)
+ previous_name = &previous_name_buf;
+ else
+ previous_name = NULL;
+
src_offset = sizeof(*hdr);
for (i = 0; i < istate->cache_nr; i++) {
struct ondisk_cache_entry *disk_ce;
struct cache_entry *ce;
+ unsigned long consumed;
disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
- ce = create_from_disk(disk_ce);
+ ce = create_from_disk(disk_ce, &consumed, previous_name);
set_index_entry(istate, i, ce);
- src_offset += ondisk_ce_size(ce);
+ src_offset += consumed;
}
+ strbuf_release(&previous_name_buf);
istate->timestamp.sec = st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
}
}
-static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
+/* Copy miscellaneous fields but not the name */
+static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
+ struct cache_entry *ce)
{
- int size = ondisk_ce_size(ce);
- struct ondisk_cache_entry *ondisk = xcalloc(1, size);
- char *name;
- int result;
-
ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
struct ondisk_cache_entry_extended *ondisk2;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
- name = ondisk2->name;
+ return ondisk2->name;
+ }
+ else {
+ return ondisk->name;
+ }
+}
+
+static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
+ struct strbuf *previous_name)
+{
+ int size;
+ struct ondisk_cache_entry *ondisk;
+ char *name;
+ int result;
+
+ if (!previous_name) {
+ size = ondisk_ce_size(ce);
+ ondisk = xcalloc(1, size);
+ name = copy_cache_entry_to_ondisk(ondisk, ce);
+ memcpy(name, ce->name, ce_namelen(ce));
+ } else {
+ int common, to_remove, prefix_size;
+ unsigned char to_remove_vi[16];
+ for (common = 0;
+ (ce->name[common] &&
+ common < previous_name->len &&
+ ce->name[common] == previous_name->buf[common]);
+ common++)
+ ; /* still matching */
+ to_remove = previous_name->len - common;
+ prefix_size = encode_varint(to_remove, to_remove_vi);
+
+ if (ce->ce_flags & CE_EXTENDED)
+ size = offsetof(struct ondisk_cache_entry_extended, name);
+ else
+ size = offsetof(struct ondisk_cache_entry, name);
+ size += prefix_size + (ce_namelen(ce) - common + 1);
+
+ ondisk = xcalloc(1, size);
+ name = copy_cache_entry_to_ondisk(ondisk, ce);
+ memcpy(name, to_remove_vi, prefix_size);
+ memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
+
+ strbuf_splice(previous_name, common, to_remove,
+ ce->name + common, ce_namelen(ce) - common);
}
- else
- name = ondisk->name;
- memcpy(name, ce->name, ce_namelen(ce));
result = ce_write(c, fd, ondisk, size);
free(ondisk);
{
git_SHA_CTX c;
struct cache_header hdr;
- int i, err, removed, extended;
+ int i, err, removed, extended, hdr_version;
struct cache_entry **cache = istate->cache;
int entries = istate->cache_nr;
struct stat st;
+ struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
}
}
+ if (!istate->version)
+ istate->version = INDEX_FORMAT_DEFAULT;
+
+ /* demote version 3 to version 2 when the latter suffices */
+ if (istate->version == 3 || istate->version == 2)
+ istate->version = extended ? 3 : 2;
+
+ hdr_version = istate->version;
+
hdr.hdr_signature = htonl(CACHE_SIGNATURE);
- /* for extended format, increase version so older git won't try to read it */
- hdr.hdr_version = htonl(extended ? 3 : 2);
+ hdr.hdr_version = htonl(hdr_version);
hdr.hdr_entries = htonl(entries - removed);
git_SHA1_Init(&c);
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
return -1;
+ previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
if (ce->ce_flags & CE_REMOVE)
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
- if (ce_write_entry(&c, newfd, ce) < 0)
+ if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
return -1;
}
+ strbuf_release(&previous_name_buf);
/* Write extension data here */
if (istate->cache_tree) {
#include "tag.h"
#include "dir.h"
-/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */
-#define REF_KNOWS_PEELED 0x10
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ * - it ends with ".lock"
+ * - it contains a "\" (backslash)
+ */
-struct ref_entry {
- unsigned char flag; /* ISSYMREF? ISPACKED? */
+/* Return true iff ch is not allowed in reference names. */
+static inline int bad_ref_char(int ch)
+{
+ if (((unsigned) ch) <= ' ' || ch == 0x7f ||
+ ch == '~' || ch == '^' || ch == ':' || ch == '\\')
+ return 1;
+ /* 2.13 Pattern Matching Notation */
+ if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
+ return 1;
+ return 0;
+}
+
+/*
+ * Try to read one refname component from the front of refname. Return
+ * the length of the component found, or -1 if the component is not
+ * legal.
+ */
+static int check_refname_component(const char *refname, int flags)
+{
+ const char *cp;
+ char last = '\0';
+
+ for (cp = refname; ; cp++) {
+ char ch = *cp;
+ if (ch == '\0' || ch == '/')
+ break;
+ if (bad_ref_char(ch))
+ return -1; /* Illegal character in refname. */
+ if (last == '.' && ch == '.')
+ return -1; /* Refname contains "..". */
+ if (last == '@' && ch == '{')
+ return -1; /* Refname contains "@{". */
+ last = ch;
+ }
+ if (cp == refname)
+ return 0; /* Component has zero length. */
+ if (refname[0] == '.') {
+ if (!(flags & REFNAME_DOT_COMPONENT))
+ return -1; /* Component starts with '.'. */
+ /*
+ * Even if leading dots are allowed, don't allow "."
+ * as a component (".." is prevented by a rule above).
+ */
+ if (refname[1] == '\0')
+ return -1; /* Component equals ".". */
+ }
+ if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+ return -1; /* Refname ends with ".lock". */
+ return cp - refname;
+}
+
+int check_refname_format(const char *refname, int flags)
+{
+ int component_len, component_count = 0;
+
+ while (1) {
+ /* We are at the start of a path component. */
+ component_len = check_refname_component(refname, flags);
+ if (component_len <= 0) {
+ if ((flags & REFNAME_REFSPEC_PATTERN) &&
+ refname[0] == '*' &&
+ (refname[1] == '\0' || refname[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ flags &= ~REFNAME_REFSPEC_PATTERN;
+ component_len = 1;
+ } else {
+ return -1;
+ }
+ }
+ component_count++;
+ if (refname[component_len] == '\0')
+ break;
+ /* Skip to next component. */
+ refname += component_len + 1;
+ }
+
+ if (refname[component_len - 1] == '.')
+ return -1; /* Refname ends with '.'. */
+ if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+ return -1; /* Refname has only one component. */
+ return 0;
+}
+
+struct ref_entry;
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a single cached reference. This data structure only
+ * occurs embedded in a union in struct ref_entry, and only when
+ * (ref_entry->flag & REF_DIR) is zero.
+ */
+struct ref_value {
unsigned char sha1[20];
unsigned char peeled[20];
- /* The full name of the reference (e.g., "refs/heads/master"): */
- char name[FLEX_ARRAY];
};
-struct ref_array {
+struct ref_cache;
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a level in the hierarchy of references. This data
+ * structure only occurs embedded in a union in struct ref_entry, and
+ * only when (ref_entry.flag & REF_DIR) is set. In that case,
+ * (ref_entry.flag & REF_INCOMPLETE) determines whether the references
+ * in the directory have already been read:
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose
+ * or packed references, already read.
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose
+ * references that hasn't been read yet (nor has any of its
+ * subdirectories).
+ *
+ * Entries within a directory are stored within a growable array of
+ * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i <
+ * sorted are sorted by their component name in strcmp() order and the
+ * remaining entries are unsorted.
+ *
+ * Loose references are read lazily, one directory at a time. When a
+ * directory of loose references is read, then all of the references
+ * in that directory are stored, and REF_INCOMPLETE stubs are created
+ * for any subdirectories, but the subdirectories themselves are not
+ * read. The reading is triggered by get_ref_dir().
+ */
+struct ref_dir {
int nr, alloc;
/*
*/
int sorted;
- struct ref_entry **refs;
+ /* A pointer to the ref_cache that contains this ref_dir. */
+ struct ref_cache *ref_cache;
+
+ struct ref_entry **entries;
};
+/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
+#define REF_KNOWS_PEELED 0x08
+
+/* ref_entry represents a directory of references */
+#define REF_DIR 0x10
+
/*
- * Parse one line from a packed-refs file. Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
+ * Entry has not yet been read from disk (used only for REF_DIR
+ * entries representing loose references)
*/
-static const char *parse_ref_line(char *line, unsigned char *sha1)
-{
+#define REF_INCOMPLETE 0x20
+
+/*
+ * A ref_entry represents either a reference or a "subdirectory" of
+ * references.
+ *
+ * Each directory in the reference namespace is represented by a
+ * ref_entry with (flags & REF_DIR) set and containing a subdir member
+ * that holds the entries in that directory that have been read so
+ * far. If (flags & REF_INCOMPLETE) is set, then the directory and
+ * its subdirectories haven't been read yet. REF_INCOMPLETE is only
+ * used for loose reference directories.
+ *
+ * References are represented by a ref_entry with (flags & REF_DIR)
+ * unset and a value member that describes the reference's value. The
+ * flag member is at the ref_entry level, but it is also needed to
+ * interpret the contents of the value field (in other words, a
+ * ref_value object is not very much use without the enclosing
+ * ref_entry).
+ *
+ * Reference names cannot end with slash and directories' names are
+ * always stored with a trailing slash (except for the top-level
+ * directory, which is always denoted by ""). This has two nice
+ * consequences: (1) when the entries in each subdir are sorted
+ * lexicographically by name (as they usually are), the references in
+ * a whole tree can be generated in lexicographic order by traversing
+ * the tree in left-to-right, depth-first order; (2) the names of
+ * references and subdirectories cannot conflict, and therefore the
+ * presence of an empty subdirectory does not block the creation of a
+ * similarly-named reference. (The fact that reference names with the
+ * same leading components can conflict *with each other* is a
+ * separate issue that is regulated by is_refname_available().)
+ *
+ * Please note that the name field contains the fully-qualified
+ * reference (or subdirectory) name. Space could be saved by only
+ * storing the relative names. But that would require the full names
+ * to be generated on the fly when iterating in do_for_each_ref(), and
+ * would break callback functions, who have always been able to assume
+ * that the name strings that they are passed will not be freed during
+ * the iteration.
+ */
+struct ref_entry {
+ unsigned char flag; /* ISSYMREF? ISPACKED? */
+ union {
+ struct ref_value value; /* if not (flags&REF_DIR) */
+ struct ref_dir subdir; /* if (flags&REF_DIR) */
+ } u;
/*
- * 42: the answer to everything.
- *
- * In this case, it happens to be the answer to
- * 40 (length of sha1 hex representation)
- * +1 (space in between hex and name)
- * +1 (newline at the end of the line)
+ * The full name of the reference (e.g., "refs/heads/master")
+ * or the full name of the directory with a trailing slash
+ * (e.g., "refs/heads/"):
*/
- int len = strlen(line) - 42;
+ char name[FLEX_ARRAY];
+};
- if (len <= 0)
- return NULL;
- if (get_sha1_hex(line, sha1) < 0)
- return NULL;
- if (!isspace(line[40]))
- return NULL;
- line += 41;
- if (isspace(*line))
- return NULL;
- if (line[len] != '\n')
- return NULL;
- line[len] = 0;
+static void read_loose_refs(const char *dirname, struct ref_dir *dir);
- return line;
+static struct ref_dir *get_ref_dir(struct ref_entry *entry)
+{
+ struct ref_dir *dir;
+ assert(entry->flag & REF_DIR);
+ dir = &entry->u.subdir;
+ if (entry->flag & REF_INCOMPLETE) {
+ read_loose_refs(entry->name, dir);
+ entry->flag &= ~REF_INCOMPLETE;
+ }
+ return dir;
}
static struct ref_entry *create_ref_entry(const char *refname,
die("Reference has invalid format: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
- hashcpy(ref->sha1, sha1);
- hashclr(ref->peeled);
+ hashcpy(ref->u.value.sha1, sha1);
+ hashclr(ref->u.value.peeled);
memcpy(ref->name, refname, len);
ref->flag = flag;
return ref;
}
-/* Add a ref_entry to the end of the ref_array (unsorted). */
-static void add_ref(struct ref_array *refs, struct ref_entry *ref)
+static void clear_ref_dir(struct ref_dir *dir);
+
+static void free_ref_entry(struct ref_entry *entry)
{
- ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc);
- refs->refs[refs->nr++] = ref;
+ if (entry->flag & REF_DIR)
+ clear_ref_dir(get_ref_dir(entry));
+ free(entry);
+}
+
+/*
+ * Add a ref_entry to the end of dir (unsorted). Entry is always
+ * stored directly in dir; no recursion into subdirectories is
+ * done.
+ */
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry)
+{
+ ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc);
+ dir->entries[dir->nr++] = entry;
+}
+
+/*
+ * Clear and free all entries in dir, recursively.
+ */
+static void clear_ref_dir(struct ref_dir *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++)
+ free_ref_entry(dir->entries[i]);
+ free(dir->entries);
+ dir->sorted = dir->nr = dir->alloc = 0;
+ dir->entries = NULL;
+}
+
+/*
+ * Create a struct ref_entry object for the specified dirname.
+ * dirname is the name of the directory with a trailing slash (e.g.,
+ * "refs/heads/") or "" for the top-level directory.
+ */
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+ const char *dirname, int incomplete)
+{
+ struct ref_entry *direntry;
+ int len = strlen(dirname);
+ direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1);
+ memcpy(direntry->name, dirname, len + 1);
+ direntry->u.subdir.ref_cache = ref_cache;
+ direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
+ return direntry;
}
static int ref_entry_cmp(const void *a, const void *b)
return strcmp(one->name, two->name);
}
+static void sort_ref_dir(struct ref_dir *dir);
+
+/*
+ * Return the entry with the given refname from the ref_dir
+ * (non-recursively), sorting dir if necessary. Return NULL if no
+ * such entry is found. dir must already be complete.
+ */
+static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname)
+{
+ struct ref_entry *e, **r;
+ int len;
+
+ if (refname == NULL || !dir->nr)
+ return NULL;
+
+ sort_ref_dir(dir);
+
+ len = strlen(refname) + 1;
+ e = xmalloc(sizeof(struct ref_entry) + len);
+ memcpy(e->name, refname, len);
+
+ r = bsearch(&e, dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
+
+ free(e);
+
+ if (r == NULL)
+ return NULL;
+
+ return *r;
+}
+
+/*
+ * Search for a directory entry directly within dir (without
+ * recursing). Sort dir if necessary. subdirname must be a directory
+ * name (i.e., end in '/'). If mkdir is set, then create the
+ * directory if it is missing; otherwise, return NULL if the desired
+ * directory cannot be found. dir must already be complete.
+ */
+static struct ref_dir *search_for_subdir(struct ref_dir *dir,
+ const char *subdirname, int mkdir)
+{
+ struct ref_entry *entry = search_ref_dir(dir, subdirname);
+ if (!entry) {
+ if (!mkdir)
+ return NULL;
+ /*
+ * Since dir is complete, the absence of a subdir
+ * means that the subdir really doesn't exist;
+ * therefore, create an empty record for it but mark
+ * the record complete.
+ */
+ entry = create_dir_entry(dir->ref_cache, subdirname, 0);
+ add_entry_to_dir(dir, entry);
+ }
+ return get_ref_dir(entry);
+}
+
+/*
+ * If refname is a reference name, find the ref_dir within the dir
+ * tree that should hold refname. If refname is a directory name
+ * (i.e., ends in '/'), then return that ref_dir itself. dir must
+ * represent the top-level directory and must already be complete.
+ * Sort ref_dirs and recurse into subdirectories as necessary. If
+ * mkdir is set, then create any missing directories; otherwise,
+ * return NULL if the desired directory cannot be found.
+ */
+static struct ref_dir *find_containing_dir(struct ref_dir *dir,
+ const char *refname, int mkdir)
+{
+ struct strbuf dirname;
+ const char *slash;
+ strbuf_init(&dirname, PATH_MAX);
+ for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ struct ref_dir *subdir;
+ strbuf_add(&dirname,
+ refname + dirname.len,
+ (slash + 1) - (refname + dirname.len));
+ subdir = search_for_subdir(dir, dirname.buf, mkdir);
+ if (!subdir) {
+ dir = NULL;
+ break;
+ }
+ dir = subdir;
+ }
+
+ strbuf_release(&dirname);
+ return dir;
+}
+
+/*
+ * Find the value entry with the given name in dir, sorting ref_dirs
+ * and recursing into subdirectories as necessary. If the name is not
+ * found or it corresponds to a directory entry, return NULL.
+ */
+static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
+{
+ struct ref_entry *entry;
+ dir = find_containing_dir(dir, refname, 0);
+ if (!dir)
+ return NULL;
+ entry = search_ref_dir(dir, refname);
+ return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
+}
+
+/*
+ * Add a ref_entry to the ref_dir (unsorted), recursing into
+ * subdirectories as necessary. dir must represent the top-level
+ * directory. Return 0 on success.
+ */
+static int add_ref(struct ref_dir *dir, struct ref_entry *ref)
+{
+ dir = find_containing_dir(dir, ref->name, 1);
+ if (!dir)
+ return -1;
+ add_entry_to_dir(dir, ref);
+ return 0;
+}
+
/*
* Emit a warning and return true iff ref1 and ref2 have the same name
* and the same sha1. Die if they have the same name but different
*/
static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
{
- if (!strcmp(ref1->name, ref2->name)) {
- /* Duplicate name; make sure that the SHA1s match: */
- if (hashcmp(ref1->sha1, ref2->sha1))
- die("Duplicated ref, and SHA1s don't match: %s",
- ref1->name);
- warning("Duplicated ref: %s", ref1->name);
- return 1;
- } else {
+ if (strcmp(ref1->name, ref2->name))
return 0;
- }
+
+ /* Duplicate name; make sure that they don't conflict: */
+
+ if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR))
+ /* This is impossible by construction */
+ die("Reference directory conflict: %s", ref1->name);
+
+ if (hashcmp(ref1->u.value.sha1, ref2->u.value.sha1))
+ die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
+
+ warning("Duplicated ref: %s", ref1->name);
+ return 1;
}
/*
- * Sort the entries in array (if they are not already sorted).
+ * Sort the entries in dir non-recursively (if they are not already
+ * sorted) and remove any duplicate entries.
*/
-static void sort_ref_array(struct ref_array *array)
+static void sort_ref_dir(struct ref_dir *dir)
{
int i, j;
+ struct ref_entry *last = NULL;
/*
* This check also prevents passing a zero-length array to qsort(),
* which is a problem on some platforms.
*/
- if (array->sorted == array->nr)
+ if (dir->sorted == dir->nr)
return;
- qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
+ qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
- /* Remove any duplicates from the ref_array */
- i = 0;
- for (j = 1; j < array->nr; j++) {
- if (is_dup_ref(array->refs[i], array->refs[j])) {
- free(array->refs[j]);
- continue;
+ /* Remove any duplicates: */
+ for (i = 0, j = 0; j < dir->nr; j++) {
+ struct ref_entry *entry = dir->entries[j];
+ if (last && is_dup_ref(last, entry))
+ free_ref_entry(entry);
+ else
+ last = dir->entries[i++] = entry;
+ }
+ dir->sorted = dir->nr = i;
+}
+
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
+
+static struct ref_entry *current_ref;
+
+static int do_one_ref(const char *base, each_ref_fn fn, int trim,
+ int flags, void *cb_data, struct ref_entry *entry)
+{
+ int retval;
+ if (prefixcmp(entry->name, base))
+ return 0;
+
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (entry->flag & REF_ISBROKEN)
+ return 0; /* ignore broken refs e.g. dangling symref */
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
}
- array->refs[++i] = array->refs[j];
}
- array->sorted = array->nr = i + 1;
+ current_ref = entry;
+ retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
+ current_ref = NULL;
+ return retval;
}
-static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
+/*
+ * Call fn for each reference in dir that has index in the range
+ * offset <= index < dir->nr. Recurse into subdirectories that are in
+ * that index range, sorting them before iterating. This function
+ * does not sort dir itself; it should be sorted beforehand.
+ */
+static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
+ const char *base,
+ each_ref_fn fn, int trim, int flags, void *cb_data)
{
- struct ref_entry *e, **r;
- int len;
+ int i;
+ assert(dir->sorted == dir->nr);
+ for (i = offset; i < dir->nr; i++) {
+ struct ref_entry *entry = dir->entries[i];
+ int retval;
+ if (entry->flag & REF_DIR) {
+ struct ref_dir *subdir = get_ref_dir(entry);
+ sort_ref_dir(subdir);
+ retval = do_for_each_ref_in_dir(subdir, 0,
+ base, fn, trim, flags, cb_data);
+ } else {
+ retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
+ }
+ if (retval)
+ return retval;
+ }
+ return 0;
+}
- if (refname == NULL)
- return NULL;
+/*
+ * Call fn for each reference in the union of dir1 and dir2, in order
+ * by refname. Recurse into subdirectories. If a value entry appears
+ * in both dir1 and dir2, then only process the version that is in
+ * dir2. The input dirs must already be sorted, but subdirs will be
+ * sorted as needed.
+ */
+static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
+ struct ref_dir *dir2,
+ const char *base, each_ref_fn fn, int trim,
+ int flags, void *cb_data)
+{
+ int retval;
+ int i1 = 0, i2 = 0;
- if (!array->nr)
- return NULL;
- sort_ref_array(array);
- len = strlen(refname) + 1;
- e = xmalloc(sizeof(struct ref_entry) + len);
- memcpy(e->name, refname, len);
+ assert(dir1->sorted == dir1->nr);
+ assert(dir2->sorted == dir2->nr);
+ while (1) {
+ struct ref_entry *e1, *e2;
+ int cmp;
+ if (i1 == dir1->nr) {
+ return do_for_each_ref_in_dir(dir2, i2,
+ base, fn, trim, flags, cb_data);
+ }
+ if (i2 == dir2->nr) {
+ return do_for_each_ref_in_dir(dir1, i1,
+ base, fn, trim, flags, cb_data);
+ }
+ e1 = dir1->entries[i1];
+ e2 = dir2->entries[i2];
+ cmp = strcmp(e1->name, e2->name);
+ if (cmp == 0) {
+ if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) {
+ /* Both are directories; descend them in parallel. */
+ struct ref_dir *subdir1 = get_ref_dir(e1);
+ struct ref_dir *subdir2 = get_ref_dir(e2);
+ sort_ref_dir(subdir1);
+ sort_ref_dir(subdir2);
+ retval = do_for_each_ref_in_dirs(
+ subdir1, subdir2,
+ base, fn, trim, flags, cb_data);
+ i1++;
+ i2++;
+ } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
+ /* Both are references; ignore the one from dir1. */
+ retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
+ i1++;
+ i2++;
+ } else {
+ die("conflict between reference and directory: %s",
+ e1->name);
+ }
+ } else {
+ struct ref_entry *e;
+ if (cmp < 0) {
+ e = e1;
+ i1++;
+ } else {
+ e = e2;
+ i2++;
+ }
+ if (e->flag & REF_DIR) {
+ struct ref_dir *subdir = get_ref_dir(e);
+ sort_ref_dir(subdir);
+ retval = do_for_each_ref_in_dir(
+ subdir, 0,
+ base, fn, trim, flags, cb_data);
+ } else {
+ retval = do_one_ref(base, fn, trim, flags, cb_data, e);
+ }
+ }
+ if (retval)
+ return retval;
+ }
+ if (i1 < dir1->nr)
+ return do_for_each_ref_in_dir(dir1, i1,
+ base, fn, trim, flags, cb_data);
+ if (i2 < dir2->nr)
+ return do_for_each_ref_in_dir(dir2, i2,
+ base, fn, trim, flags, cb_data);
+ return 0;
+}
- r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
+/*
+ * Return true iff refname1 and refname2 conflict with each other.
+ * Two reference names conflict if one of them exactly matches the
+ * leading components of the other; e.g., "foo/bar" conflicts with
+ * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
+ * "foo/barbados".
+ */
+static int names_conflict(const char *refname1, const char *refname2)
+{
+ for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
+ ;
+ return (*refname1 == '\0' && *refname2 == '/')
+ || (*refname1 == '/' && *refname2 == '\0');
+}
- free(e);
+struct name_conflict_cb {
+ const char *refname;
+ const char *oldrefname;
+ const char *conflicting_refname;
+};
- if (r == NULL)
- return NULL;
+static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
+ if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
+ return 0;
+ if (names_conflict(data->refname, existingrefname)) {
+ data->conflicting_refname = existingrefname;
+ return 1;
+ }
+ return 0;
+}
- return *r;
+/*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference in array. If
+ * oldrefname is non-NULL, ignore potential conflicts with oldrefname
+ * (e.g., because oldrefname is scheduled for deletion in the same
+ * operation).
+ */
+static int is_refname_available(const char *refname, const char *oldrefname,
+ struct ref_dir *dir)
+{
+ struct name_conflict_cb data;
+ data.refname = refname;
+ data.oldrefname = oldrefname;
+ data.conflicting_refname = NULL;
+
+ sort_ref_dir(dir);
+ if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
+ 0, DO_FOR_EACH_INCLUDE_BROKEN,
+ &data)) {
+ error("'%s' exists; cannot create '%s'",
+ data.conflicting_refname, refname);
+ return 0;
+ }
+ return 1;
}
/*
*/
static struct ref_cache {
struct ref_cache *next;
- char did_loose;
- char did_packed;
- struct ref_array loose;
- struct ref_array packed;
+ struct ref_entry *loose;
+ struct ref_entry *packed;
/* The submodule name, or "" for the main repo. */
char name[FLEX_ARRAY];
} *ref_cache;
-static struct ref_entry *current_ref;
-
-static void clear_ref_array(struct ref_array *array)
-{
- int i;
- for (i = 0; i < array->nr; i++)
- free(array->refs[i]);
- free(array->refs);
- array->sorted = array->nr = array->alloc = 0;
- array->refs = NULL;
-}
-
static void clear_packed_ref_cache(struct ref_cache *refs)
{
- if (refs->did_packed)
- clear_ref_array(&refs->packed);
- refs->did_packed = 0;
+ if (refs->packed) {
+ free_ref_entry(refs->packed);
+ refs->packed = NULL;
+ }
}
static void clear_loose_ref_cache(struct ref_cache *refs)
{
- if (refs->did_loose)
- clear_ref_array(&refs->loose);
- refs->did_loose = 0;
+ if (refs->loose) {
+ free_ref_entry(refs->loose);
+ refs->loose = NULL;
+ }
}
static struct ref_cache *create_ref_cache(const char *submodule)
clear_loose_ref_cache(refs);
}
-static void read_packed_refs(FILE *f, struct ref_array *array)
+/*
+ * Parse one line from a packed-refs file. Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+ /*
+ * 42: the answer to everything.
+ *
+ * In this case, it happens to be the answer to
+ * 40 (length of sha1 hex representation)
+ * +1 (space in between hex and name)
+ * +1 (newline at the end of the line)
+ */
+ int len = strlen(line) - 42;
+
+ if (len <= 0)
+ return NULL;
+ if (get_sha1_hex(line, sha1) < 0)
+ return NULL;
+ if (!isspace(line[40]))
+ return NULL;
+ line += 41;
+ if (isspace(*line))
+ return NULL;
+ if (line[len] != '\n')
+ return NULL;
+ line[len] = 0;
+
+ return line;
+}
+
+static void read_packed_refs(FILE *f, struct ref_dir *dir)
{
struct ref_entry *last = NULL;
char refline[PATH_MAX];
refname = parse_ref_line(refline, sha1);
if (refname) {
last = create_ref_entry(refname, sha1, flag, 1);
- add_ref(array, last);
+ add_ref(dir, last);
continue;
}
if (last &&
strlen(refline) == 42 &&
refline[41] == '\n' &&
!get_sha1_hex(refline + 1, sha1))
- hashcpy(last->peeled, sha1);
+ hashcpy(last->u.value.peeled, sha1);
}
}
-static struct ref_array *get_packed_refs(struct ref_cache *refs)
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
{
- if (!refs->did_packed) {
+ if (!refs->packed) {
const char *packed_refs_file;
FILE *f;
+ refs->packed = create_dir_entry(refs, "", 0);
if (*refs->name)
packed_refs_file = git_path_submodule(refs->name, "packed-refs");
else
packed_refs_file = git_path("packed-refs");
f = fopen(packed_refs_file, "r");
if (f) {
- read_packed_refs(f, &refs->packed);
+ read_packed_refs(f, get_ref_dir(refs->packed));
fclose(f);
}
- refs->did_packed = 1;
}
- return &refs->packed;
+ return get_ref_dir(refs->packed);
}
void add_packed_ref(const char *refname, const unsigned char *sha1)
create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
-static void get_ref_dir(struct ref_cache *refs, const char *base,
- struct ref_array *array)
+/*
+ * Read the loose references from the namespace dirname into dir
+ * (without recursing). dirname must end with '/'. dir must be the
+ * directory entry corresponding to dirname.
+ */
+static void read_loose_refs(const char *dirname, struct ref_dir *dir)
{
- DIR *dir;
+ struct ref_cache *refs = dir->ref_cache;
+ DIR *d;
const char *path;
+ struct dirent *de;
+ int dirnamelen = strlen(dirname);
+ struct strbuf refname;
if (*refs->name)
- path = git_path_submodule(refs->name, "%s", base);
+ path = git_path_submodule(refs->name, "%s", dirname);
else
- path = git_path("%s", base);
+ path = git_path("%s", dirname);
+ d = opendir(path);
+ if (!d)
+ return;
- dir = opendir(path);
-
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *refname = xmalloc(baselen + 257);
-
- memcpy(refname, base, baselen);
- if (baselen && base[baselen-1] != '/')
- refname[baselen++] = '/';
+ strbuf_init(&refname, dirnamelen + 257);
+ strbuf_add(&refname, dirname, dirnamelen);
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
- struct stat st;
- int flag;
- int namelen;
- const char *refdir;
+ while ((de = readdir(d)) != NULL) {
+ unsigned char sha1[20];
+ struct stat st;
+ int flag;
+ const char *refdir;
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(refname + baselen, de->d_name, namelen+1);
- refdir = *refs->name
- ? git_path_submodule(refs->name, "%s", refname)
- : git_path("%s", refname);
- if (stat(refdir, &st) < 0)
- continue;
- if (S_ISDIR(st.st_mode)) {
- get_ref_dir(refs, refname, array);
- continue;
- }
+ if (de->d_name[0] == '.')
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ strbuf_addstr(&refname, de->d_name);
+ refdir = *refs->name
+ ? git_path_submodule(refs->name, "%s", refname.buf)
+ : git_path("%s", refname.buf);
+ if (stat(refdir, &st) < 0) {
+ ; /* silently ignore */
+ } else if (S_ISDIR(st.st_mode)) {
+ strbuf_addch(&refname, '/');
+ add_entry_to_dir(dir,
+ create_dir_entry(refs, refname.buf, 1));
+ } else {
if (*refs->name) {
hashclr(sha1);
flag = 0;
- if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) {
+ if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) {
hashclr(sha1);
flag |= REF_ISBROKEN;
}
- } else if (read_ref_full(refname, sha1, 1, &flag)) {
+ } else if (read_ref_full(refname.buf, sha1, 1, &flag)) {
hashclr(sha1);
flag |= REF_ISBROKEN;
}
- add_ref(array, create_ref_entry(refname, sha1, flag, 1));
+ add_entry_to_dir(dir,
+ create_ref_entry(refname.buf, sha1, flag, 1));
}
- free(refname);
- closedir(dir);
+ strbuf_setlen(&refname, dirnamelen);
}
+ strbuf_release(&refname);
+ closedir(d);
}
-struct warn_if_dangling_data {
- FILE *fp;
- const char *refname;
- const char *msg_fmt;
-};
-
-static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
-{
- struct warn_if_dangling_data *d = cb_data;
- const char *resolves_to;
- unsigned char junk[20];
-
- if (!(flags & REF_ISSYMREF))
- return 0;
-
- resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
- if (!resolves_to || strcmp(resolves_to, d->refname))
- return 0;
-
- fprintf(d->fp, d->msg_fmt, refname);
- return 0;
-}
-
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
-{
- struct warn_if_dangling_data data;
-
- data.fp = fp;
- data.refname = refname;
- data.msg_fmt = msg_fmt;
- for_each_rawref(warn_if_dangling_symref, &data);
-}
-
-static struct ref_array *get_loose_refs(struct ref_cache *refs)
+static struct ref_dir *get_loose_refs(struct ref_cache *refs)
{
- if (!refs->did_loose) {
- get_ref_dir(refs, "refs", &refs->loose);
- refs->did_loose = 1;
+ if (!refs->loose) {
+ /*
+ * Mark the top-level directory complete because we
+ * are about to read the only subdirectory that can
+ * hold references:
+ */
+ refs->loose = create_dir_entry(refs, "", 0);
+ /*
+ * Create an incomplete entry for "refs/":
+ */
+ add_entry_to_dir(get_ref_dir(refs->loose),
+ create_dir_entry(refs, "refs/", 1));
}
- return &refs->loose;
+ return get_ref_dir(refs->loose);
}
/* We allow "recursive" symbolic refs. Only within reason, though */
const char *refname, unsigned char *sha1)
{
struct ref_entry *ref;
- struct ref_array *array = get_packed_refs(refs);
+ struct ref_dir *dir = get_packed_refs(refs);
- ref = search_ref_array(array, refname);
+ ref = find_ref(dir, refname);
if (ref == NULL)
return -1;
- memcpy(sha1, ref->sha1, 20);
+ memcpy(sha1, ref->u.value.sha1, 20);
return 0;
}
*/
static int get_packed_ref(const char *refname, unsigned char *sha1)
{
- struct ref_array *packed = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *entry = search_ref_array(packed, refname);
+ struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+ struct ref_entry *entry = find_ref(packed, refname);
if (entry) {
- hashcpy(sha1, entry->sha1);
+ hashcpy(sha1, entry->u.value.sha1);
return 0;
}
return -1;
return read_ref_full(refname, sha1, 1, NULL);
}
-#define DO_FOR_EACH_INCLUDE_BROKEN 01
-static int do_one_ref(const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data, struct ref_entry *entry)
+int ref_exists(const char *refname)
{
- if (prefixcmp(entry->name, base))
- return 0;
-
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
- if (entry->flag & REF_ISBROKEN)
- return 0; /* ignore broken refs e.g. dangling symref */
- if (!has_sha1_file(entry->sha1)) {
- error("%s does not point to a valid object!", entry->name);
- return 0;
- }
- }
- current_ref = entry;
- return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
+ unsigned char sha1[20];
+ return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
}
static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
if (current_ref && (current_ref->name == refname
|| !strcmp(current_ref->name, refname))) {
if (current_ref->flag & REF_KNOWS_PEELED) {
- hashcpy(sha1, current_ref->peeled);
+ hashcpy(sha1, current_ref->u.value.peeled);
return 0;
}
- hashcpy(base, current_ref->sha1);
+ hashcpy(base, current_ref->u.value.sha1);
goto fallback;
}
return -1;
if ((flag & REF_ISPACKED)) {
- struct ref_array *array = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *r = search_ref_array(array, refname);
+ struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
+ struct ref_entry *r = find_ref(dir, refname);
if (r != NULL && r->flag & REF_KNOWS_PEELED) {
- hashcpy(sha1, r->peeled);
+ hashcpy(sha1, r->u.value.peeled);
return 0;
}
}
return -1;
}
+struct warn_if_dangling_data {
+ FILE *fp;
+ const char *refname;
+ const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct warn_if_dangling_data *d = cb_data;
+ const char *resolves_to;
+ unsigned char junk[20];
+
+ if (!(flags & REF_ISSYMREF))
+ return 0;
+
+ resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
+ if (!resolves_to || strcmp(resolves_to, d->refname))
+ return 0;
+
+ fprintf(d->fp, d->msg_fmt, refname);
+ fputc('\n', d->fp);
+ return 0;
+}
+
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+{
+ struct warn_if_dangling_data data;
+
+ data.fp = fp;
+ data.refname = refname;
+ data.msg_fmt = msg_fmt;
+ for_each_rawref(warn_if_dangling_symref, &data);
+}
+
static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
int trim, int flags, void *cb_data)
{
- int retval = 0, p = 0, l = 0;
struct ref_cache *refs = get_ref_cache(submodule);
- struct ref_array *packed = get_packed_refs(refs);
- struct ref_array *loose = get_loose_refs(refs);
-
- sort_ref_array(packed);
- sort_ref_array(loose);
- while (p < packed->nr && l < loose->nr) {
- struct ref_entry *entry;
- int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
- if (!cmp) {
- p++;
- continue;
- }
- if (cmp > 0) {
- entry = loose->refs[l++];
- } else {
- entry = packed->refs[p++];
- }
- retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
- if (retval)
- goto end_each;
- }
-
- if (l < loose->nr) {
- p = l;
- packed = loose;
- }
+ struct ref_dir *packed_dir = get_packed_refs(refs);
+ struct ref_dir *loose_dir = get_loose_refs(refs);
+ int retval = 0;
- for (; p < packed->nr; p++) {
- retval = do_one_ref(base, fn, trim, flags, cb_data, packed->refs[p]);
- if (retval)
- goto end_each;
+ if (base && *base) {
+ packed_dir = find_containing_dir(packed_dir, base, 0);
+ loose_dir = find_containing_dir(loose_dir, base, 0);
+ }
+
+ if (packed_dir && loose_dir) {
+ sort_ref_dir(packed_dir);
+ sort_ref_dir(loose_dir);
+ retval = do_for_each_ref_in_dirs(
+ packed_dir, loose_dir,
+ base, fn, trim, flags, cb_data);
+ } else if (packed_dir) {
+ sort_ref_dir(packed_dir);
+ retval = do_for_each_ref_in_dir(
+ packed_dir, 0,
+ base, fn, trim, flags, cb_data);
+ } else if (loose_dir) {
+ sort_ref_dir(loose_dir);
+ retval = do_for_each_ref_in_dir(
+ loose_dir, 0,
+ base, fn, trim, flags, cb_data);
}
-end_each:
- current_ref = NULL;
return retval;
}
-
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-/*
- * Make sure "ref" is something reasonable to have under ".git/refs/";
- * We do not like it if:
- *
- * - any path component of it begins with ".", or
- * - it has double dots "..", or
- * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it ends with a "/".
- * - it ends with ".lock"
- * - it contains a "\" (backslash)
- */
-
-/* Return true iff ch is not allowed in reference names. */
-static inline int bad_ref_char(int ch)
-{
- if (((unsigned) ch) <= ' ' || ch == 0x7f ||
- ch == '~' || ch == '^' || ch == ':' || ch == '\\')
- return 1;
- /* 2.13 Pattern Matching Notation */
- if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
- return 1;
- return 0;
-}
-
-/*
- * Try to read one refname component from the front of refname. Return
- * the length of the component found, or -1 if the component is not
- * legal.
- */
-static int check_refname_component(const char *refname, int flags)
-{
- const char *cp;
- char last = '\0';
-
- for (cp = refname; ; cp++) {
- char ch = *cp;
- if (ch == '\0' || ch == '/')
- break;
- if (bad_ref_char(ch))
- return -1; /* Illegal character in refname. */
- if (last == '.' && ch == '.')
- return -1; /* Refname contains "..". */
- if (last == '@' && ch == '{')
- return -1; /* Refname contains "@{". */
- last = ch;
- }
- if (cp == refname)
- return -1; /* Component has zero length. */
- if (refname[0] == '.') {
- if (!(flags & REFNAME_DOT_COMPONENT))
- return -1; /* Component starts with '.'. */
- /*
- * Even if leading dots are allowed, don't allow "."
- * as a component (".." is prevented by a rule above).
- */
- if (refname[1] == '\0')
- return -1; /* Component equals ".". */
- }
- if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
- return -1; /* Refname ends with ".lock". */
- return cp - refname;
-}
-
-int check_refname_format(const char *refname, int flags)
-{
- int component_len, component_count = 0;
-
- while (1) {
- /* We are at the start of a path component. */
- component_len = check_refname_component(refname, flags);
- if (component_len < 0) {
- if ((flags & REFNAME_REFSPEC_PATTERN) &&
- refname[0] == '*' &&
- (refname[1] == '\0' || refname[1] == '/')) {
- /* Accept one wildcard as a full refname component. */
- flags &= ~REFNAME_REFSPEC_PATTERN;
- component_len = 1;
- } else {
- return -1;
- }
- }
- component_count++;
- if (refname[component_len] == '\0')
- break;
- /* Skip to next component. */
- refname += component_len + 1;
- }
-
- if (refname[component_len - 1] == '.')
- return -1; /* Refname ends with '.'. */
- if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
- return -1; /* Refname has only one component. */
- return 0;
-}
-
const char *prettify_refname(const char *name)
{
return name + (
return result;
}
-/*
- * Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference. If oldrefname
- * is non-NULL, ignore potential conflicts with oldrefname (e.g.,
- * because oldrefname is scheduled for deletion in the same
- * operation).
- */
-static int is_refname_available(const char *refname, const char *oldrefname,
- struct ref_array *array)
-{
- int i, namlen = strlen(refname); /* e.g. 'foo/bar' */
- for (i = 0; i < array->nr; i++ ) {
- struct ref_entry *entry = array->refs[i];
- /* entry->name could be 'foo' or 'foo/bar/baz' */
- if (!oldrefname || strcmp(oldrefname, entry->name)) {
- int len = strlen(entry->name);
- int cmplen = (namlen < len) ? namlen : len;
- const char *lead = (namlen < len) ? entry->name : refname;
- if (!strncmp(refname, entry->name, cmplen) &&
- lead[cmplen] == '/') {
- error("'%s' exists; cannot create '%s'",
- entry->name, refname);
- return 0;
- }
- }
- }
- return 1;
-}
-
/*
* *string and *len will only be substituted, and *string returned (for
* later free()ing) if the string passed in is a magic short-hand form
return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
}
+struct repack_without_ref_sb {
+ const char *refname;
+ int fd;
+};
+
+static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct repack_without_ref_sb *data = cb_data;
+ char line[PATH_MAX + 100];
+ int len;
+
+ if (!strcmp(data->refname, refname))
+ return 0;
+ len = snprintf(line, sizeof(line), "%s %s\n",
+ sha1_to_hex(sha1), refname);
+ /* this should not happen but just being defensive */
+ if (len > sizeof(line))
+ die("too long a refname '%s'", refname);
+ write_or_die(data->fd, line, len);
+ return 0;
+}
+
static struct lock_file packlock;
static int repack_without_ref(const char *refname)
{
- struct ref_array *packed;
- int fd, i;
-
- packed = get_packed_refs(get_ref_cache(NULL));
- if (search_ref_array(packed, refname) == NULL)
+ struct repack_without_ref_sb data;
+ struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+ if (find_ref(packed, refname) == NULL)
return 0;
- fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (fd < 0) {
+ data.refname = refname;
+ data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+ if (data.fd < 0) {
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
}
-
- for (i = 0; i < packed->nr; i++) {
- char line[PATH_MAX + 100];
- int len;
- struct ref_entry *ref = packed->refs[i];
-
- if (!strcmp(refname, ref->name))
- continue;
- len = snprintf(line, sizeof(line), "%s %s\n",
- sha1_to_hex(ref->sha1), ref->name);
- /* this should not happen but just being defensive */
- if (len > sizeof(line))
- die("too long a refname '%s'", ref->name);
- write_or_die(fd, line, len);
- }
+ do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
return commit_lock_file(&packlock);
}
return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
}
-static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
+/*
+ * Call fn for each reflog in the namespace indicated by name. name
+ * must be empty or end with '/'. Name will be used as a scratch
+ * space, but its contents will be restored before return.
+ */
+static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data)
{
- DIR *dir = opendir(git_path("logs/%s", base));
+ DIR *d = opendir(git_path("logs/%s", name->buf));
int retval = 0;
+ struct dirent *de;
+ int oldlen = name->len;
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *log = xmalloc(baselen + 257);
+ if (!d)
+ return name->len ? errno : 0;
- memcpy(log, base, baselen);
- if (baselen && base[baselen-1] != '/')
- log[baselen++] = '/';
-
- while ((de = readdir(dir)) != NULL) {
- struct stat st;
- int namelen;
+ while ((de = readdir(d)) != NULL) {
+ struct stat st;
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(log + baselen, de->d_name, namelen+1);
- if (stat(git_path("logs/%s", log), &st) < 0)
- continue;
+ if (de->d_name[0] == '.')
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ strbuf_addstr(name, de->d_name);
+ if (stat(git_path("logs/%s", name->buf), &st) < 0) {
+ ; /* silently ignore */
+ } else {
if (S_ISDIR(st.st_mode)) {
- retval = do_for_each_reflog(log, fn, cb_data);
+ strbuf_addch(name, '/');
+ retval = do_for_each_reflog(name, fn, cb_data);
} else {
unsigned char sha1[20];
- if (read_ref_full(log, sha1, 0, NULL))
- retval = error("bad ref for %s", log);
+ if (read_ref_full(name->buf, sha1, 0, NULL))
+ retval = error("bad ref for %s", name->buf);
else
- retval = fn(log, sha1, 0, cb_data);
+ retval = fn(name->buf, sha1, 0, cb_data);
}
if (retval)
break;
}
- free(log);
- closedir(dir);
+ strbuf_setlen(name, oldlen);
}
- else if (*base)
- return errno;
+ closedir(d);
return retval;
}
int for_each_reflog(each_ref_fn fn, void *cb_data)
{
- return do_for_each_reflog("", fn, cb_data);
+ int retval;
+ struct strbuf name;
+ strbuf_init(&name, PATH_MAX);
+ retval = do_for_each_reflog(&name, fn, cb_data);
+ strbuf_release(&name);
+ return retval;
}
int update_ref(const char *action, const char *refname,
return 0;
}
-int ref_exists(const char *refname)
-{
- unsigned char sha1[20];
- return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
-}
-
struct ref *find_ref_by_name(const struct ref *list, const char *name)
{
for ( ; list; list = list->next)
#define REF_ISBROKEN 0x04
/*
- * Calls the specified function for each ref file until it returns nonzero,
- * and returns the value
+ * Calls the specified function for each ref file until it returns
+ * nonzero, and returns the value. Please note that it is not safe to
+ * modify references while an iteration is in progress, unless the
+ * same callback function invocation that modifies the reference also
+ * returns a nonzero value to immediately stop the iteration.
*/
typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
extern int head_ref(each_ref_fn, void *);
}
}
+void reset_revision_walk(void)
+{
+ clear_object_flags(SEEN | ADDED | SHOWN);
+}
+
int prepare_revision_walk(struct rev_info *revs)
{
int nr = revs->pending.nr;
struct object_array_entry *e, *list;
+ struct commit_list **next = &revs->commits;
e = list = revs->pending.objects;
revs->pending.nr = 0;
if (commit) {
if (!(commit->object.flags & SEEN)) {
commit->object.flags |= SEEN;
- commit_list_insert_by_date(commit, &revs->commits);
+ next = commit_list_append(commit, next);
}
}
e++;
}
+ commit_list_sort_by_date(&revs->commits);
if (!revs->leak_pending)
free(list);
const char * const usagestr[]);
extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
+extern void reset_revision_walk(void);
extern int prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
#include "sigchain.h"
#include "argv-array.h"
+#ifndef SHELL_PATH
+# define SHELL_PATH "/bin/sh"
+#endif
+
struct child_to_clean {
pid_t pid;
struct child_to_clean *next;
die("BUG: shell command is empty");
if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+#ifndef WIN32
+ nargv[nargc++] = SHELL_PATH;
+#else
nargv[nargc++] = "sh";
+#endif
nargv[nargc++] = "-c";
if (argc < 2)
#include "rerere.h"
#include "merge-recursive.h"
#include "refs.h"
+#include "argv-array.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
return !clean;
}
+static int is_index_unchanged(void)
+{
+ unsigned char head_sha1[20];
+ struct commit *head_commit;
+
+ if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+ return error(_("Could not resolve HEAD commit\n"));
+
+ head_commit = lookup_commit(head_sha1);
+
+ /*
+ * If head_commit is NULL, check_commit, called from
+ * lookup_commit, would have indicated that head_commit is not
+ * a commit object already. parse_commit() will return failure
+ * without further complaints in such a case. Otherwise, if
+ * the commit is invalid, parse_commit() will complain. So
+ * there is nothing for us to say here. Just return failure.
+ */
+ if (parse_commit(head_commit))
+ return -1;
+
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ if (!cache_tree_fully_valid(active_cache_tree))
+ if (cache_tree_update(active_cache_tree, active_cache,
+ active_nr, 0))
+ return error(_("Unable to update cache tree\n"));
+
+ return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
/*
* If we are cherry-pick, and if the merge did not result in
* hand-editing, we will hit this commit and inherit the original
*/
static int run_git_commit(const char *defmsg, struct replay_opts *opts)
{
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int i = 0;
+ struct argv_array array;
+ int rc;
+
+ argv_array_init(&array);
+ argv_array_push(&array, "commit");
+ argv_array_push(&array, "-n");
- args[i++] = "commit";
- args[i++] = "-n";
if (opts->signoff)
- args[i++] = "-s";
+ argv_array_push(&array, "-s");
if (!opts->edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
+ argv_array_push(&array, "-F");
+ argv_array_push(&array, defmsg);
+ }
+
+ if (opts->allow_empty)
+ argv_array_push(&array, "--allow-empty");
+
+ rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+ argv_array_clear(&array);
+ return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+ const unsigned char *ptree_sha1;
+
+ if (parse_commit(commit))
+ return error(_("Could not parse commit %s\n"),
+ sha1_to_hex(commit->object.sha1));
+ if (commit->parents) {
+ struct commit *parent = commit->parents->item;
+ if (parse_commit(parent))
+ return error(_("Could not parse parent commit %s\n"),
+ sha1_to_hex(parent->object.sha1));
+ ptree_sha1 = parent->tree->object.sha1;
+ } else {
+ ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
}
- args[i] = NULL;
- return run_command_v_opt(args, RUN_GIT_CMD);
+ return !hashcmp(ptree_sha1, commit->tree->object.sha1);
}
static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
char *defmsg = NULL;
struct strbuf msgbuf = STRBUF_INIT;
int res;
+ int empty_commit;
+ int index_unchanged;
if (opts->no_commit) {
/*
free_commit_list(remotes);
}
+ empty_commit = is_original_commit_empty(commit);
+ if (empty_commit < 0)
+ return empty_commit;
+
/*
* If the merge was clean or if it failed due to conflict, we write
* CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
print_advice(res == 1, opts);
rerere(opts->allow_rerere_auto);
} else {
+ index_unchanged = is_index_unchanged();
+ /*
+ * If index_unchanged is less than 0, that indicates we either
+ * couldn't parse HEAD or the index, so error out here.
+ */
+ if (index_unchanged < 0)
+ return index_unchanged;
+
+ if (!empty_commit && !opts->keep_redundant_commits && index_unchanged)
+ /*
+ * The head tree and the index match
+ * meaning the commit is empty. Since it wasn't created
+ * empty (based on the previous test), we can conclude
+ * the commit has been made redundant. Since we don't
+ * want to keep redundant commits, we can just return
+ * here, skipping this commit
+ */
+ return 0;
+
if (!opts->no_commit)
res = run_git_commit(defmsg, opts);
}
rollback_lock_file(&index_lock);
}
-/*
- * Append a commit to the end of the commit_list.
- *
- * next starts by pointing to the variable that holds the head of an
- * empty commit_list, and is updated to point to the "next" field of
- * the last item on the list as new commits are appended.
- *
- * Usage example:
- *
- * struct commit_list *list;
- * struct commit_list **next = &list;
- *
- * next = commit_list_append(c1, next);
- * next = commit_list_append(c2, next);
- * assert(commit_list_count(list) == 2);
- * return list;
- */
-static struct commit_list **commit_list_append(struct commit *commit,
- struct commit_list **next)
-{
- struct commit_list *new = xmalloc(sizeof(struct commit_list));
- new->item = commit;
- *next = new;
- new->next = NULL;
- return &new->next;
-}
-
static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
struct replay_opts *opts)
{
int signoff;
int allow_ff;
int allow_rerere_auto;
+ int allow_empty;
+ int keep_redundant_commits;
int mainline;
#include "pack-revindex.h"
#include "sha1-lookup.h"
#include "bulk-checkin.h"
+#include "streaming.h"
#ifndef O_NOATIME
#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
return NULL;
}
-int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+/*
+ * With an in-core object data in "map", rehash it to make sure the
+ * object name actually matches "sha1" to detect object corruption.
+ * With "map" == NULL, try reading the object named with "sha1" using
+ * the streaming interface and rehash it to do the same.
+ */
+int check_sha1_signature(const unsigned char *sha1, void *map,
+ unsigned long size, const char *type)
{
unsigned char real_sha1[20];
- hash_sha1_file(map, size, type, real_sha1);
+ enum object_type obj_type;
+ struct git_istream *st;
+ git_SHA_CTX c;
+ char hdr[32];
+ int hdrlen;
+
+ if (map) {
+ hash_sha1_file(map, size, type, real_sha1);
+ return hashcmp(sha1, real_sha1) ? -1 : 0;
+ }
+
+ st = open_istream(sha1, &obj_type, &size, NULL);
+ if (!st)
+ return -1;
+
+ /* Generate the header */
+ hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
+
+ /* Sha1.. */
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+ for (;;) {
+ char buf[1024 * 16];
+ ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+ if (!readlen)
+ break;
+ git_SHA1_Update(&c, buf, readlen);
+ }
+ git_SHA1_Final(real_sha1, &c);
+ close_istream(st);
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
unlink_or_warn(tmpfile);
if (ret) {
if (ret != EEXIST) {
- return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
+ return error("unable to write sha1 filename %s: %s", filename, strerror(ret));
}
/* FIXME!!! Collision check here ? */
}
fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
if (fd < 0) {
if (errno == EACCES)
- return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
+ return error("insufficient permission for adding an object to repository database %s", get_object_directory());
else
- return error("unable to create temporary sha1 filename %s: %s\n", tmp_file, strerror(errno));
+ return error("unable to create temporary file: %s", strerror(errno));
}
/* Set it up */
len = cp + tmp_len - name;
cp = xstrndup(name, cp - name);
upstream = branch_get(*cp ? cp : NULL);
- if (!upstream
- || !upstream->merge
- || !upstream->merge[0]->dst)
- return error("No upstream branch found for '%s'", cp);
+ /*
+ * Upstream can be NULL only if cp refers to HEAD and HEAD
+ * points to something different than a branch.
+ */
+ if (!upstream)
+ return error(_("HEAD does not point to a branch"));
+ if (!upstream->merge || !upstream->merge[0]->dst) {
+ if (!ref_exists(upstream->refname))
+ return error(_("No such branch: '%s'"), cp);
+ if (!upstream->merge)
+ return error(_("No upstream configured for branch '%s'"),
+ upstream->name);
+ return error(
+ _("Upstream branch '%s' not stored as a remote-tracking branch"),
+ upstream->merge[0]->src);
+ }
free(cp);
cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
strbuf_reset(buf);
{
strbuf_add_urlencode(sb, s, strlen(s), reserved);
}
+
+void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ strbuf_vaddf(sb, fmt, ap);
+ va_end(ap);
+ strbuf_addch(sb, '\n');
+}
+
+int printf_ln(const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = vprintf(fmt, ap);
+ va_end(ap);
+ if (ret < 0 || putchar('\n') == EOF)
+ return -1;
+ return ret + 1;
+}
+
+int fprintf_ln(FILE *fp, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = vfprintf(fp, fmt, ap);
+ va_end(ap);
+ if (ret < 0 || putc('\n', fp) == EOF)
+ return -1;
+ return ret + 1;
+}
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
__attribute__((format (printf,2,0)))
extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
+__attribute__((format (printf,2,3)))
+extern void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...);
extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
int reserved);
+__attribute__((format (printf,1,2)))
+extern int printf_ln(const char *fmt, ...);
+__attribute__((format (printf,2,3)))
+extern int fprintf_ln(FILE *fp, const char *fmt, ...);
+
#endif /* STRBUF_H */
return r;
}
-ssize_t read_istream(struct git_istream *st, char *buf, size_t sz)
+ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
{
return st->vtbl->read(st, buf, sz);
}
return st->u.incore.buf ? 0 : -1;
}
+
+
+/****************************************************************
+ * Users of streaming interface
+ ****************************************************************/
+
+int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *filter,
+ int can_seek)
+{
+ struct git_istream *st;
+ enum object_type type;
+ unsigned long sz;
+ ssize_t kept = 0;
+ int result = -1;
+
+ st = open_istream(sha1, &type, &sz, filter);
+ if (!st)
+ return result;
+ if (type != OBJ_BLOB)
+ goto close_and_exit;
+ for (;;) {
+ char buf[1024 * 16];
+ ssize_t wrote, holeto;
+ ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+ if (!readlen)
+ break;
+ if (can_seek && sizeof(buf) == readlen) {
+ for (holeto = 0; holeto < readlen; holeto++)
+ if (buf[holeto])
+ break;
+ if (readlen == holeto) {
+ kept += holeto;
+ continue;
+ }
+ }
+
+ if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+ goto close_and_exit;
+ else
+ kept = 0;
+ wrote = write_in_full(fd, buf, readlen);
+
+ if (wrote != readlen)
+ goto close_and_exit;
+ }
+ if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+ write(fd, "", 1) != 1))
+ goto close_and_exit;
+ result = 0;
+
+ close_and_exit:
+ close_istream(st);
+ return result;
+}
extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *);
extern int close_istream(struct git_istream *);
-extern ssize_t read_istream(struct git_istream *, char *, size_t);
+extern ssize_t read_istream(struct git_istream *, void *, size_t);
+
+extern int stream_blob_to_fd(int fd, const unsigned char *, struct stream_filter *, int can_seek);
#endif /* STREAMING_H */
void *data)
{
int i;
- int *needs_pushing = data;
+ struct string_list *needs_pushing = data;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
if (!S_ISGITLINK(p->two->mode))
continue;
- if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
- *needs_pushing = 1;
- break;
- }
+ if (submodule_needs_pushing(p->two->path, p->two->sha1))
+ string_list_insert(needs_pushing, p->two->path);
}
}
-
-static void commit_need_pushing(struct commit *commit, int *needs_pushing)
+static void find_unpushed_submodule_commits(struct commit *commit,
+ struct string_list *needs_pushing)
{
struct rev_info rev;
diff_tree_combined_merge(commit, 1, &rev);
}
-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
+int find_unpushed_submodules(unsigned char new_sha1[20],
+ const char *remotes_name, struct string_list *needs_pushing)
{
struct rev_info rev;
struct commit *commit;
const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
int argc = ARRAY_SIZE(argv) - 1;
char *sha1_copy;
- int needs_pushing = 0;
+
struct strbuf remotes_arg = STRBUF_INIT;
strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
- while ((commit = get_revision(&rev)) && !needs_pushing)
- commit_need_pushing(commit, &needs_pushing);
+ while ((commit = get_revision(&rev)) != NULL)
+ find_unpushed_submodule_commits(commit, needs_pushing);
+ reset_revision_walk();
free(sha1_copy);
strbuf_release(&remotes_arg);
- return needs_pushing;
+ return needs_pushing->nr;
+}
+
+static int push_submodule(const char *path)
+{
+ if (add_submodule_odb(path))
+ return 1;
+
+ if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+ struct child_process cp;
+ const char *argv[] = {"push", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.dir = path;
+ if (run_command(&cp))
+ return 0;
+ close(cp.out);
+ }
+
+ return 1;
+}
+
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
+{
+ int i, ret = 1;
+ struct string_list needs_pushing;
+
+ memset(&needs_pushing, 0, sizeof(struct string_list));
+ needs_pushing.strdup_strings = 1;
+
+ if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
+ return 1;
+
+ for (i = 0; i < needs_pushing.nr; i++) {
+ const char *path = needs_pushing.items[i].string;
+ fprintf(stderr, "Pushing submodule '%s'\n", path);
+ if (!push_submodule(path)) {
+ fprintf(stderr, "Unable to push submodule '%s'\n", path);
+ ret = 0;
+ }
+ }
+
+ string_list_clear(&needs_pushing, 0);
+
+ return ret;
}
static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
if (in_merge_bases(b, &commit, 1))
add_object_array(o, NULL, &merges);
}
+ reset_revision_walk();
/* Now we've got all merges that contain a and b. Prune all
* merges that contain another found merge and save them in
unsigned is_submodule_modified(const char *path, int ignore_untracked);
int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
const unsigned char a[20], const unsigned char b[20], int search);
-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
+int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
+ struct string_list *needs_pushing);
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
#endif
#
-# Library code for git-p4 tests
+# Library code for git p4 tests
#
. ./test-lib.sh
if ! test_have_prereq PYTHON; then
- skip_all='skipping git-p4 tests; python not available'
+ skip_all='skipping git p4 tests; python not available'
test_done
fi
( p4 -h && p4d -h ) >/dev/null 2>&1 || {
- skip_all='skipping git-p4 tests; no p4 or p4d'
+ skip_all='skipping git p4 tests; no p4 or p4d'
test_done
}
-GITP4="$GIT_BUILD_DIR/contrib/fast-import/git-p4"
-
# Try to pick a unique port: guess a large number, then hope
# no more than one of each test is running.
#
export P4PORT=localhost:$P4DPORT
export P4CLIENT=client
+export P4EDITOR=:
db="$TRASH_DIRECTORY/db"
cli="$TRASH_DIRECTORY/cli"
--- /dev/null
+#!/bin/sh
+
+test_description="Tests index-pack performance"
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+
+test_expect_success 'repack' '
+ git repack -ad &&
+ PACK=`ls .git/objects/pack/*.pack | head -n1` &&
+ test -f "$PACK" &&
+ export PACK
+'
+
+test_perf 'index-pack 0 threads' '
+ GIT_DIR=t1 git index-pack --threads=1 --stdin < $PACK
+'
+
+test_perf 'index-pack 1 thread ' '
+ GIT_DIR=t2 GIT_FORCE_THREADS=1 git index-pack --threads=1 --stdin < $PACK
+'
+
+test_perf 'index-pack 2 threads' '
+ GIT_DIR=t3 git index-pack --threads=2 --stdin < $PACK
+'
+
+test_perf 'index-pack 4 threads' '
+ GIT_DIR=t4 git index-pack --threads=4 --stdin < $PACK
+'
+
+test_perf 'index-pack 8 threads' '
+ GIT_DIR=t5 git index-pack --threads=8 --stdin < $PACK
+'
+
+test_perf 'index-pack default number of threads' '
+ GIT_DIR=t6 git index-pack --stdin < $PACK
+'
+
+test_done
Subject: test1
---
- foo | 1 +
+ foo | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 foo
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2012 Heiko Voigt
+#
+
+test_description='Test revision walking api'
+
+. ./test-lib.sh
+
+cat >run_twice_expected <<-EOF
+1st
+ > add b
+ > add a
+2nd
+ > add b
+ > add a
+EOF
+
+test_expect_success 'setup' '
+ echo a > a &&
+ git add a &&
+ git commit -m "add a" &&
+ echo b > b &&
+ git add b &&
+ git commit -m "add b"
+'
+
+test_expect_success 'revision walking can be done twice' '
+ test-revision-walking run-twice > run_twice_actual
+ test_cmp run_twice_expected run_twice_actual
+'
+
+test_done
#!/bin/sh
-test_description='external credential helper tests'
-. ./test-lib.sh
-. "$TEST_DIRECTORY"/lib-credential.sh
+test_description='external credential helper tests
-pre_test() {
- test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
- eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+This is a tool for authors of external helper tools to sanity-check
+their helpers. If you have written the "git-credential-foo" helper,
+you check it with:
+
+ make GIT_TEST_CREDENTIAL_HELPER=foo t0303-credential-external.sh
+
+This assumes that your helper is capable of both storing and
+retrieving credentials (some helpers may be read-only, and they will
+fail these tests).
+
+Please note that the individual tests do not verify all of the
+preconditions themselves, but rather build on each other. A failing
+test means that tests later in the sequence can return false "OK"
+results.
+
+If your helper supports time-based expiration with a configurable
+timeout, you can test that feature with:
+
+ make GIT_TEST_CREDENTIAL_HELPER=foo \
+ GIT_TEST_CREDENTIAL_HELPER_TIMEOUT="foo --timeout=1" \
+ t0303-credential-external.sh
- # clean before the test in case there is cruft left
- # over from a previous run that would impact results
- helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
-}
+If your helper requires additional setup before the tests are started,
+you can set GIT_TEST_CREDENTIAL_HELPER_SETUP to a sequence of shell
+commands.
+'
-post_test() {
- # clean afterwards so that we are good citizens
- # and don't leave cruft in the helper's storage, which
- # might be long-term system storage
- helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
-}
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
- say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)"
-else
- pre_test
- helper_test "$GIT_TEST_CREDENTIAL_HELPER"
- post_test
+ skip_all="used to test external credential helpers"
+ test_done
fi
+test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
+ eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+
+# clean before the test in case there is cruft left
+# over from a previous run that would impact results
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
+helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+
if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
- say "# skipping external helper timeout tests"
+ say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)"
else
- pre_test
helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
- post_test
fi
+# clean afterwards so that we are good citizens
+# and don't leave cruft in the helper's storage, which
+# might be long-term system storage
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
test_done
. ./test-lib.sh
test_expect_success setup '
- git config core.bigfilethreshold 200k &&
+ # clone does not allow us to pass core.bigfilethreshold to
+ # new repos, so set core.bigfilethreshold globally
+ git config --global core.bigfilethreshold 200k &&
echo X | dd of=large1 bs=1k seek=2000 &&
echo X | dd of=large2 bs=1k seek=2000 &&
echo X | dd of=large3 bs=1k seek=2000 &&
- echo Y | dd of=huge bs=1k seek=2500
+ echo Y | dd of=huge bs=1k seek=2500 &&
+ GIT_ALLOC_LIMIT=1500 &&
+ export GIT_ALLOC_LIMIT
'
test_expect_success 'add a large file or two' '
)
'
+test_expect_success 'diff --raw' '
+ git commit -q -m initial &&
+ echo modified >>large1 &&
+ git add large1 &&
+ git commit -q -m modified &&
+ git diff --raw HEAD^
+'
+
+test_expect_success 'hash-object' '
+ git hash-object large1
+'
+
+test_expect_success 'cat-file a large file' '
+ git cat-file blob :large1 >/dev/null
+'
+
+test_expect_success 'cat-file a large file from a tag' '
+ git tag -m largefile largefiletag :large1 &&
+ git cat-file blob largefiletag >/dev/null
+'
+
+test_expect_success 'git-show a large file' '
+ git show :large1 >/dev/null
+
+'
+
+test_expect_success 'repack' '
+ git repack -ad
+'
+
+test_expect_success 'tar achiving' '
+ git archive --format=tar HEAD >/dev/null
+'
+
+test_expect_success 'zip achiving, store only' '
+ git archive --format=zip -0 HEAD >/dev/null
+'
+
+test_expect_success 'zip achiving, deflate' '
+ git archive --format=zip HEAD >/dev/null
+'
+
test_done
cat > resolve.expect << EOF
Updating VARIABLE..VARIABLE
FASTFORWARD (no commit created; -m option ignored)
- example | 1 +
- hello | 1 +
+ example | 1 +
+ hello | 1 +
2 files changed, 2 insertions(+)
EOF
test_commit 3 &&
(cd clone &&
test_commit 4 &&
- git branch --track my-side origin/side)
-
+ git branch --track my-side origin/side &&
+ git branch --track local-master master &&
+ git remote add -t master master-only .. &&
+ git fetch master-only &&
+ git branch bad-upstream &&
+ git config branch.bad-upstream.remote master-only &&
+ git config branch.bad-upstream.merge refs/heads/side
+ )
'
+sq="'"
+
full_name () {
(cd clone &&
git rev-parse --symbolic-full-name "$@")
git show -s --pretty=format:%s "$@")
}
+error_message () {
+ (cd clone &&
+ test_must_fail git rev-parse --verify "$@")
+}
+
test_expect_success '@{upstream} resolves to correct full name' '
test refs/remotes/origin/master = "$(full_name @{upstream})"
'
test_expect_success 'merge my-side@{u} records the correct name' '
(
- sq="'\''" &&
cd clone || exit
git checkout master || exit
git branch -D new ;# can fail but is ok
test_cmp expect actual
'
+test_expect_success 'branch@{u} works when tracking a local branch' '
+ test refs/heads/master = "$(full_name local-master@{u})"
+'
+
+test_expect_success 'branch@{u} error message when no upstream' '
+ cat >expect <<-EOF &&
+ error: No upstream configured for branch ${sq}non-tracking${sq}
+ fatal: Needed a single revision
+ EOF
+ error_message non-tracking@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when no upstream' '
+ cat >expect <<-EOF &&
+ error: No upstream configured for branch ${sq}master${sq}
+ fatal: Needed a single revision
+ EOF
+ test_must_fail git rev-parse --verify @{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message with misspelt branch' '
+ cat >expect <<-EOF &&
+ error: No such branch: ${sq}no-such-branch${sq}
+ fatal: Needed a single revision
+ EOF
+ error_message no-such-branch@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when not on a branch' '
+ cat >expect <<-EOF &&
+ error: HEAD does not point to a branch
+ fatal: Needed a single revision
+ EOF
+ git checkout HEAD^0 &&
+ test_must_fail git rev-parse --verify @{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message if upstream branch not fetched' '
+ cat >expect <<-EOF &&
+ error: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+ fatal: Needed a single revision
+ EOF
+ error_message bad-upstream@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'pull works when tracking a local branch' '
+(
+ cd clone &&
+ git checkout local-master &&
+ git pull
+)
+'
+
+# makes sense if the previous one succeeded
+test_expect_success '@{u} works when tracking a local branch' '
+ test refs/heads/master = "$(full_name @{u})"
+'
+
cat >expect <<EOF
commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
Reflog: master@{0} (C O Mitter <committer@example.com>)
git config branch.child.merge refs/heads/master &&
git checkout child^ &&
git checkout child >stdout &&
- test_cmp expect stdout
+ test_i18ncmp expect stdout
'
test_done
test_path_is_missing .git/refs/heads/t
'
+test_expect_success 'git branch --column' '
+ COLUMNS=81 git branch --column=column >actual &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column with an extremely long branch name' '
+ long=this/is/a/part/of/long/branch/name &&
+ long=z$long/$long/$long/$long &&
+ test_when_finished "git branch -d $long" &&
+ git branch $long &&
+ COLUMNS=80 git branch --column=column >actual &&
+ cat >expected <<EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+ $long
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch with column.*' '
+ git config column.ui column &&
+ git config column.branch "dense" &&
+ COLUMNS=80 git branch >actual &&
+ git config --unset column.branch &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column -v should fail' '
+ test_must_fail git branch --column -v
+'
+
+test_expect_success 'git branch -v with column.ui ignored' '
+ git config column.ui column &&
+ COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+EOF
+ test_cmp expected actual
+'
+
mv .git/config .git/config-saved
test_expect_success 'git branch -m q q2 without config should succeed' '
p1='tabs ," (dq) and spaces'
p2='just space'
-cat >"$p0" <<\EOF
-1. A quick brown fox jumps over the lazy cat, oops dog.
-2. A quick brown fox jumps over the lazy cat, oops dog.
-3. A quick brown fox jumps over the lazy cat, oops dog.
-EOF
-
-cat 2>/dev/null >"$p1" "$p0"
-echo 'Foo Bar Baz' >"$p2"
+test_expect_success 'setup' '
+ cat >"$p0" <<-\EOF &&
+ 1. A quick brown fox jumps over the lazy cat, oops dog.
+ 2. A quick brown fox jumps over the lazy cat, oops dog.
+ 3. A quick brown fox jumps over the lazy cat, oops dog.
+ EOF
+
+ { cat "$p0" >"$p1" || :; } &&
+ { echo "Foo Bar Baz" >"$p2" || :; } &&
+
+ if test -f "$p1" && cmp "$p0" "$p1"
+ then
+ test_set_prereq TABS_IN_FILENAMES
+ fi
+'
-if test -f "$p1" && cmp "$p0" "$p1"
+if ! test_have_prereq TABS_IN_FILENAMES
then
- test_set_prereq TABS_IN_FILENAMES
-else
# since FAT/NTFS does not allow tabs in filenames, skip this test
- say 'Your filesystem does not allow tabs in filenames'
+ skip_all='Your filesystem does not allow tabs in filenames'
+ test_done
fi
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny' >expected
-"
+test_expect_success 'setup: populate index and tree' '
+ git update-index --add "$p0" "$p2" &&
+ t0=$(git write-tree)
+'
-test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \
- 'git update-index --add "$p0" "$p2" &&
+test_expect_success 'ls-files prints space in filename verbatim' '
+ printf "%s\n" "just space" no-funny >expected &&
git ls-files >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t0=`git write-tree` &&
-echo "$t0" >t0 &&
+ test_cmp expected current
+'
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
+test_expect_success 'setup: add funny filename' '
+ git update-index --add "$p1" &&
+ t1=$(git write-tree)
'
-test_expect_success TABS_IN_FILENAMES 'git ls-files with-funny' \
- 'git update-index --add "$p1" &&
+test_expect_success 'ls-files quotes funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ "tabs\t,\" (dq) and spaces"
+ EOF
git ls-files >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny
-tabs ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \
- 'git ls-files -z | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t1=`git write-tree` &&
-echo "$t1" >t1 &&
-
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git ls-tree with funny' \
- 'git ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-A "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index with-funny' \
- 'git diff-index --name-status $t0 >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree with-funny' \
- 'git diff-tree --name-status $t0 $t1 >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'A
-tabs ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index -z with-funny' \
- 'git diff-index -z --name-status $t0 | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -z with-funny' \
- 'git diff-tree -z --name-status $t0 $t1 | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-CNUM no-funny "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -C with-funny' \
- 'git diff-tree -C --find-copies-harder --name-status \
- $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-RNUM no-funny "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git update-index --force-remove "$p0" &&
- git diff-index -M --name-status \
- $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git diff-index -M -p $t0 |
- sed -e "s/index [0-9]*%/index NUM%/" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-chmod +x "$p1" &&
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-old mode 100644
-new mode 100755
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git diff-index -M -p $t0 |
- sed -e "s/index [0-9]*%/index NUM%/" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat >expected <<\EOF
- "tabs\t,\" (dq) and spaces"
- 1 file changed, 0 insertions(+), 0 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree rename with-funny applied' \
- 'git diff-index -M -p $t0 |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
- no-funny
- "tabs\t,\" (dq) and spaces"
- 2 files changed, 3 insertions(+), 3 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny applied' \
- 'git diff-index -p $t0 |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git apply non-git diff' \
- 'git diff-index -p $t0 |
- sed -ne "/^[-+@]/p" |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
+ test_cmp expected current
+'
+
+test_expect_success 'ls-files -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ tabs ," (dq) and spaces
+ EOF
+ git ls-files -z >ls-files.z &&
+ perl -pe "y/\000/\012/" <ls-files.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'ls-tree quotes funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ "tabs\t,\" (dq) and spaces"
+ EOF
+ git ls-tree -r $t1 >ls-tree &&
+ sed -e "s/^[^ ]* //" <ls-tree >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index --name-status quotes funny filename' '
+ cat >expected <<-\EOF &&
+ A "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index --name-status $t0 >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree --name-status quotes funny filename' '
+ cat >expected <<-\EOF &&
+ A "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-tree --name-status $t0 $t1 >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ A
+ tabs ," (dq) and spaces
+ EOF
+ git diff-index -z --name-status $t0 >diff-index.z &&
+ perl -pe "y/\000/\012/" <diff-index.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ A
+ tabs ," (dq) and spaces
+ EOF
+ git diff-tree -z --name-status $t0 $t1 >diff-tree.z &&
+ perl -pe y/\\000/\\012/ <diff-tree.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree --find-copies-harder quotes funny filename' '
+ cat >expected <<-\EOF &&
+ CNUM no-funny "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-tree -C --find-copies-harder --name-status $t0 $t1 >out &&
+ sed -e "s/^C[0-9]*/CNUM/" <out >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'setup: remove unfunny index entry' '
+ git update-index --force-remove "$p0"
+'
+
+test_expect_success 'diff-tree -M quotes funny filename' '
+ cat >expected <<-\EOF &&
+ RNUM no-funny "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M --name-status $t0 >out &&
+ sed -e "s/^R[0-9]*/RNUM/" <out >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index -M -p quotes funny filename' '
+ cat >expected <<-\EOF &&
+ diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+ similarity index NUM%
+ rename from no-funny
+ rename to "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'setup: mode change' '
+ chmod +x "$p1"
+'
+
+test_expect_success 'diff-index -M -p with mode change quotes funny filename' '
+ cat >expected <<-\EOF &&
+ diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+ old mode 100644
+ new mode 100755
+ similarity index NUM%
+ rename from no-funny
+ rename to "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diffstat for rename quotes funny filename' '
+ cat >expected <<-\EOF &&
+ "tabs\t,\" (dq) and spaces"
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ git apply --stat <diff >diffstat &&
+ sed -e "s/|.*//" -e "s/ *\$//" <diffstat >current &&
+ test_i18ncmp expected current
+'
+
+test_expect_success 'numstat for rename quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'numstat without -M quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 3 no-funny
+ 3 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -p $t0 >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'numstat for non-git rename diff quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 3 no-funny
+ 3 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -p $t0 >git-diff &&
+ sed -ne "/^[-+@]/p" <git-diff >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
test_done
echo resolved > file1 &&
git add file1 &&
git rebase --continue > output &&
- grep "^ file1 | 2 +-$" output
+ grep "^ file1 | 2 +-$" output
'
test_expect_success 'multi-squash only fires up editor once' '
# And rebase G1..M1 onto E2
test_expect_success 'rebase two levels of merge' '
+ git checkout A1 &&
test_commit G1 &&
test_commit H1 &&
test_commit I1 &&
echo third >> file1 &&
git add file1 &&
test_tick &&
- git commit --allow-empty-message -m ""
+ git commit --allow-empty-message -m "" &&
+
+ git checkout master &&
+ git checkout -b empty-branch2 &&
+ test_tick &&
+ git commit --allow-empty -m "empty"
'
'
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+ git checkout master &&
+ echo fourth >>file2 &&
+ git add file2 &&
+ git commit -m "fourth" &&
+ test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+ git checkout master &&
+ git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+ git checkout master &&
+ git cherry-pick --keep-redundant-commits HEAD^
+'
+
test_done
'
test_expect_success 'cherry-pick first..fourth works' '
+ git checkout -f master &&
+ git reset --hard first &&
+ test_tick &&
+ git cherry-pick first..fourth &&
+ git diff --quiet other &&
+ git diff --quiet HEAD other &&
+ check_head_differs_from fourth
+'
+
+test_expect_success 'output to keep user entertained during multi-pick' '
cat <<-\EOF >expected &&
[master OBJID] second
Author: A U Thor <author@example.com>
git reset --hard first &&
test_tick &&
git cherry-pick first..fourth >actual &&
+ sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+ test_line_count -ge 3 actual.fuzzy &&
+ test_i18ncmp expected actual.fuzzy
+'
+
+test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+ git checkout -f master &&
+ git reset --hard first &&
+ test_tick &&
+ git cherry-pick --strategy resolve first..fourth &&
git diff --quiet other &&
git diff --quiet HEAD other &&
-
- sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
- test_cmp expected actual.fuzzy &&
check_head_differs_from fourth
'
-test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+test_expect_success 'output during multi-pick indicates merge strategy' '
cat <<-\EOF >expected &&
Trying simple merge.
[master OBJID] second
git reset --hard first &&
test_tick &&
git cherry-pick --strategy resolve first..fourth >actual &&
- git diff --quiet other &&
- git diff --quiet HEAD other &&
sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
- test_cmp expected actual.fuzzy &&
- check_head_differs_from fourth
+ test_i18ncmp expected actual.fuzzy
'
test_expect_success 'cherry-pick --ff first..fourth works' '
test $(git ls-files --modified | wc -l) -eq 1
'
-test_expect_success 'stash show - stashes on stack, stash-like argument' '
+test_expect_success 'stash show format defaults to --stat' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
STASH_ID=$(git stash create) &&
git reset --hard &&
cat >expected <<-EOF &&
- file | 1 +
+ file | 1 +
1 file changed, 1 insertion(+)
EOF
git stash show ${STASH_ID} >actual &&
+ test_i18ncmp expected actual
+'
+
+test_expect_success 'stash show - stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ echo bar >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ echo "1 0 file" >expected &&
+ git stash show --numstat ${STASH_ID} >actual &&
test_cmp expected actual
'
echo foo >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
- cat >expected <<-EOF &&
- file | 1 +
- 1 file changed, 1 insertion(+)
- EOF
- git stash show ${STASH_ID} >actual &&
+ echo "1 0 file" >expected &&
+ git stash show --numstat ${STASH_ID} >actual &&
test_cmp expected actual
'
'
. ./test-lib.sh
-test_expect_success \
- 'setup' \
- 'echo frotz >rezrov &&
- git update-index --add rezrov &&
- tree=`git write-tree` &&
- echo $tree'
-
-test_expect_success \
- 'chmod' \
- 'test_chmod +x rezrov &&
- git diff-index $tree >current'
-
-sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
-echo ":100644 100755 X X M rezrov" >expected
-
-test_expect_success \
- 'verify' \
- 'test_cmp expected check'
+sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /'
+
+test_expect_success 'setup' '
+ echo frotz >rezrov &&
+ git update-index --add rezrov &&
+ tree=`git write-tree` &&
+ echo $tree
+'
+
+test_expect_success 'chmod' '
+ test_chmod +x rezrov &&
+ git diff-index $tree >current &&
+ sed -e "$sed_script" <current >check &&
+ echo ":100644 100755 X X M rezrov" >expected &&
+ test_cmp expected check
+'
+
+test_expect_success 'prepare binary file' '
+ git commit -m rezrov &&
+ printf "\00\01\02\03\04\05\06" >binbin &&
+ git add binbin &&
+ git commit -m binbin
+'
+
+test_expect_success '--stat output after text chmod' '
+ test_chmod -x rezrov &&
+ echo " 0 files changed" >expect &&
+ git diff HEAD --stat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after text chmod' '
+ git diff HEAD --shortstat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--stat output after binary chmod' '
+ test_chmod +x binbin &&
+ echo " 0 files changed" >expect &&
+ git diff HEAD --stat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after binary chmod' '
+ git diff HEAD --shortstat >actual &&
+ test_cmp expect actual
+'
test_done
. ./test-lib.sh
+cat >expect.binary-numstat <<\EOF
+1 1 a
+- - b
+1 1 c
+- - d
+EOF
+
test_expect_success 'prepare repository' \
'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
git update-index --add a b c d &&
d | Bin
4 files changed, 2 insertions(+), 2 deletions(-)
EOF
-test_expect_success 'diff without --binary' \
- 'git diff | git apply --stat --summary >current &&
- test_cmp expected current'
+test_expect_success '"apply --stat" output for binary file change' '
+ git diff >diff &&
+ git apply --stat --summary <diff >current &&
+ test_i18ncmp expected current
+'
+
+test_expect_success 'apply --numstat notices binary file change' '
+ git diff >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expect.binary-numstat current
+'
-test_expect_success 'diff with --binary' \
- 'git diff --binary | git apply --stat --summary >current &&
- test_cmp expected current'
+test_expect_success 'apply --numstat understands diff --binary format' '
+ git diff --binary >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expect.binary-numstat current
+'
# apply needs to be able to skip the binary material correctly
# in order to report the line number of a corrupt patch.
test_cmp expected actual
'
+cat >expect <<EOF
+ binfile | Bin 0 -> 1026 bytes
+ textfile | 10000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+
+test_expect_success 'diff --stat with binary files and big change count' '
+ echo X | dd of=binfile bs=1k seek=1 &&
+ git add binfile &&
+ i=0 &&
+ while test $i -lt 10000; do
+ echo $i &&
+ i=$(($i + 1))
+ done >textfile &&
+ git add textfile &&
+ git diff --cached --stat binfile textfile >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+'
+
test_done
} >"$actual" &&
if test -f "$expect"
then
- test_cmp "$expect" "$actual" &&
+ case $cmd in
+ *format-patch* | *-stat*)
+ test_i18ncmp "$expect" "$actual";;
+ *)
+ test_cmp "$expect" "$actual";;
+ esac &&
rm -f "$actual"
else
# this is to help developing new tests.
$ git diff-tree --cc --patch-with-stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --cc dir/sub
$ git diff-tree --cc --patch-with-stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
$ git diff-tree --cc --patch-with-stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --cc dir/sub
$ git diff-tree --cc --stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
$
$ git diff-tree --cc --stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
$
$ git diff-tree --cc --stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
$
$ git diff-tree --pretty=oneline --root --patch-with-stat initial
444ac553ac7612cc88969031b02b3767fb8a353a Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
$
$ git diff-tree --root --patch-with-stat initial
444ac553ac7612cc88969031b02b3767fb8a353a
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
$ git diff-tree -c --stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
$
$ git diff-tree -c --stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
$
$ git diff-tree -c --stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
$
$ git diff --patch-with-stat -r initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
$ git diff --patch-with-stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
$ git diff --stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
$
$ git diff -r --stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
$
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Second
Third
- dir/sub | 4 ++++
- file0 | 3 +++
- file1 | 3 +++
- file2 | 3 ---
+ dir/sub | 4 ++++
+ file0 | 3 +++
+ file1 | 3 +++
+ file2 | 3 ---
4 files changed, 10 insertions(+), 3 deletions(-)
create mode 100644 file1
delete mode 100644 file2
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Subject: [DIFFERENT_PREFIX 2/2] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Subject: [PATCH] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Subject: [PATCH] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Subject: [PATCH 2/3] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Subject: [PATCH 3/3] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Subject: [PATCH 2/3] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Subject: [PATCH 3/3] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Subject: [PATCH 2/2] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
Subject: [PATCH] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Side
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
Side
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --cc dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --combined dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
$
Side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
$
Side
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
Side
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
+ dir/sub | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --cc dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
2 files changed, 5 insertions(+)
diff --combined dir/sub
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
3 files changed, 9 insertions(+)
create mode 100644 file3
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
+ dir/sub | 2 ++
+ file1 | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 file1
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
'
cat > expect << EOF
----
- file | 16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
-
-diff --git a/file b/file
index 40f36c6..2dc5c23 100644
--- a/file
+++ b/file
test_expect_success 'format-patch respects -U' '
git format-patch -U4 -2 &&
- sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+ sed -e "1,/^diff/d" -e "/^+5/q" \
+ <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch \
+ >output &&
test_cmp expect output
'
test_cmp expect actual
'
-test_expect_success TABS_IN_FILENAMES 'setup expected files' '
-cat >expect <<\EOF
- pathname.1 => "Rpathname\twith HT.0" | 0
- pathname.3 => "Rpathname\nwith LF.0" | 0
- "pathname\twith HT.3" => "Rpathname\nwith LF.1" | 0
- pathname.2 => Rpathname with SP.0 | 0
- "pathname\twith HT.2" => Rpathname with SP.1 | 0
- pathname.0 => Rpathname.0 | 0
- "pathname\twith HT.0" => Rpathname.1 | 0
- 7 files changed, 0 insertions(+), 0 deletions(-)
-EOF
+test_expect_success TABS_IN_FILENAMES 'git diff --numstat -M HEAD' '
+ cat >expect <<-\EOF &&
+ 0 0 pathname.1 => "Rpathname\twith HT.0"
+ 0 0 pathname.3 => "Rpathname\nwith LF.0"
+ 0 0 "pathname\twith HT.3" => "Rpathname\nwith LF.1"
+ 0 0 pathname.2 => Rpathname with SP.0
+ 0 0 "pathname\twith HT.2" => Rpathname with SP.1
+ 0 0 pathname.0 => Rpathname.0
+ 0 0 "pathname\twith HT.0" => Rpathname.1
+ EOF
+ git diff --numstat -M HEAD >actual &&
+ test_cmp expect actual
'
test_expect_success TABS_IN_FILENAMES 'git diff --stat -M HEAD' '
+ cat >expect <<-\EOF &&
+ pathname.1 => "Rpathname\twith HT.0" | 0
+ pathname.3 => "Rpathname\nwith LF.0" | 0
+ "pathname\twith HT.3" => "Rpathname\nwith LF.1" | 0
+ pathname.2 => Rpathname with SP.0 | 0
+ "pathname\twith HT.2" => Rpathname with SP.1 | 0
+ pathname.0 => Rpathname.0 | 0
+ "pathname\twith HT.0" => Rpathname.1 | 0
+ 7 files changed, 0 insertions(+), 0 deletions(-)
+ EOF
git diff --stat -M HEAD >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_done
'
cat >expect.stat <<'EOF'
- file | Bin 2 -> 4 bytes
+ file | Bin 2 -> 4 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
EOF
test_expect_success 'diffstat does not run textconv' '
echo file diff=fail >.gitattributes &&
git diff --stat HEAD^ HEAD >actual &&
- test_cmp expect.stat actual
+ test_i18ncmp expect.stat actual &&
+
+ head -n1 <expect.stat >expect.line1 &&
+ head -n1 <actual >actual.line1 &&
+ test_cmp expect.line1 actual.line1
'
# restore working setup
echo file diff=foo >.gitattributes
grep "GIT binary patch" diff
'
-test_expect_success 'rewrite diff --stat shows binary changes' '
+test_expect_success 'rewrite diff --numstat shows binary changes' '
+ git diff -B --numstat --summary >diff &&
+ grep -e "- - " diff &&
+ grep " rewrite file" diff
+'
+
+test_expect_success 'diff --stat counts binary rewrite as 0 lines' '
git diff -B --stat --summary >diff &&
grep "Bin" diff &&
- grep "0 insertions.*0 deletions" diff &&
+ test_i18ngrep "0 insertions.*0 deletions" diff &&
grep " rewrite file" diff
'
'
cat > expected <<\EOF
- bar => sub/bar | Bin 5 -> 5 bytes
- foo => sub/foo | 0
- 2 files changed, 0 insertions(+), 0 deletions(-)
+- - bar => sub/bar
+0 0 foo => sub/foo
diff --git a/bar b/sub/bar
similarity index 100%
EOF
test_expect_success 'git show -C -C report renames' '
- git show -C -C --raw --binary --stat | tail -n 12 > current &&
+ git show -C -C --raw --binary --numstat >patch-with-stat &&
+ tail -n 11 patch-with-stat >current &&
test_cmp expected current
'
"
}
+check_numstat() {
+expect=$1; shift
+cat >expected <<EOF
+1 0 $expect
+EOF
+test_expect_success "--numstat $*" "
+ echo '1 0 $expect' >expected &&
+ git diff --numstat $* HEAD^ >actual &&
+ test_cmp expected actual
+"
+}
+
check_stat() {
expect=$1; shift
cat >expected <<EOF
- $expect | 1 +
+ $expect | 1 +
1 file changed, 1 insertion(+)
EOF
test_expect_success "--stat $*" "
git diff --stat $* HEAD^ >actual &&
- test_cmp expected actual
+ test_i18ncmp expected actual
"
}
"
}
-for type in diff stat raw; do
+for type in diff numstat stat raw; do
check_$type file2 --relative=subdir/
check_$type file2 --relative=subdir
check_$type dir/file2 --relative=sub
'
cat <<EOF >expect_diff_stat
- changed/text | 2 +-
- dst/copy/changed/text | 10 ++++++++++
- dst/copy/rearranged/text | 10 ++++++++++
- dst/copy/unchanged/text | 10 ++++++++++
- dst/move/changed/text | 10 ++++++++++
- dst/move/rearranged/text | 10 ++++++++++
- dst/move/unchanged/text | 10 ++++++++++
- rearranged/text | 2 +-
- src/move/changed/text | 10 ----------
- src/move/rearranged/text | 10 ----------
- src/move/unchanged/text | 10 ----------
- 11 files changed, 62 insertions(+), 32 deletions(-)
+1 1 changed/text
+10 0 dst/copy/changed/text
+10 0 dst/copy/rearranged/text
+10 0 dst/copy/unchanged/text
+10 0 dst/move/changed/text
+10 0 dst/move/rearranged/text
+10 0 dst/move/unchanged/text
+1 1 rearranged/text
+0 10 src/move/changed/text
+0 10 src/move/rearranged/text
+0 10 src/move/unchanged/text
EOF
cat <<EOF >expect_diff_stat_M
- changed/text | 2 +-
- dst/copy/changed/text | 10 ++++++++++
- dst/copy/rearranged/text | 10 ++++++++++
- dst/copy/unchanged/text | 10 ++++++++++
- {src => dst}/move/changed/text | 2 +-
- {src => dst}/move/rearranged/text | 2 +-
- {src => dst}/move/unchanged/text | 0
- rearranged/text | 2 +-
- 8 files changed, 34 insertions(+), 4 deletions(-)
+1 1 changed/text
+10 0 dst/copy/changed/text
+10 0 dst/copy/rearranged/text
+10 0 dst/copy/unchanged/text
+1 1 {src => dst}/move/changed/text
+1 1 {src => dst}/move/rearranged/text
+0 0 {src => dst}/move/unchanged/text
+1 1 rearranged/text
EOF
cat <<EOF >expect_diff_stat_CC
- changed/text | 2 +-
- {src => dst}/copy/changed/text | 2 +-
- {src => dst}/copy/rearranged/text | 2 +-
- {src => dst}/copy/unchanged/text | 0
- {src => dst}/move/changed/text | 2 +-
- {src => dst}/move/rearranged/text | 2 +-
- {src => dst}/move/unchanged/text | 0
- rearranged/text | 2 +-
- 8 files changed, 6 insertions(+), 6 deletions(-)
-EOF
-
-test_expect_success 'sanity check setup (--stat)' '
- git diff --stat HEAD^..HEAD >actual_diff_stat &&
+1 1 changed/text
+1 1 {src => dst}/copy/changed/text
+1 1 {src => dst}/copy/rearranged/text
+0 0 {src => dst}/copy/unchanged/text
+1 1 {src => dst}/move/changed/text
+1 1 {src => dst}/move/rearranged/text
+0 0 {src => dst}/move/unchanged/text
+1 1 rearranged/text
+EOF
+
+test_expect_success 'sanity check setup (--numstat)' '
+ git diff --numstat HEAD^..HEAD >actual_diff_stat &&
test_cmp expect_diff_stat actual_diff_stat &&
- git diff --stat -M HEAD^..HEAD >actual_diff_stat_M &&
+ git diff --numstat -M HEAD^..HEAD >actual_diff_stat_M &&
test_cmp expect_diff_stat_M actual_diff_stat_M &&
- git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
+ git diff --numstat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
test_cmp expect_diff_stat_CC actual_diff_stat_CC
'
echo a >a &&
echo b >b &&
cat >expect <<-\EOF
- a | 1 +
- b | 1 +
+ a | 1 +
+ b | 1 +
2 files changed, 2 insertions(+)
EOF
git diff --stat --stat-count=2 >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_done
while read cmd args
do
cat >expect <<-'EOF'
- ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd: small change with long name gives more space to the name" '
git $cmd $args >output &&
'
cat >expect <<-'EOF'
- ...aaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" '
git $cmd $args --stat=40 >output &&
'
cat >expect <<-'EOF'
- ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd --stat=...,name-width with long name" '
git $cmd $args --stat=60,30 >output &&
test_expect_success "$title" '
git apply --stat --summary \
<"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current &&
- test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+ test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
'
test_expect_success "$title with recount" '
sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" |
git apply --recount --stat --summary >current &&
- test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+ test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
'
done <<\EOF
rename
git reset --hard &&
touch empty-file &&
test_tick &&
- { git am empty-file > actual 2>&1 && false || :; } &&
+ test_must_fail git am empty-file 2>actual &&
echo Patch format detection failed. >expected &&
- test_cmp expected actual
+ test_i18ncmp expected actual
'
test_done
| |
| | reach
| | ---
-| | reach.t | 1 +
+| | reach.t | 1 +
| | 1 file changed, 1 insertion(+)
| |
| | diff --git a/reach.t b/reach.t
| | |
| | | octopus-b
| | | ---
-| | | octopus-b.t | 1 +
+| | | octopus-b.t | 1 +
| | | 1 file changed, 1 insertion(+)
| | |
| | | diff --git a/octopus-b.t b/octopus-b.t
| |
| | octopus-a
| | ---
-| | octopus-a.t | 1 +
+| | octopus-a.t | 1 +
| | 1 file changed, 1 insertion(+)
| |
| | diff --git a/octopus-a.t b/octopus-a.t
|
| seventh
| ---
-| seventh.t | 1 +
+| seventh.t | 1 +
| 1 file changed, 1 insertion(+)
|
| diff --git a/seventh.t b/seventh.t
| | | |
| | | | tangle-a
| | | | ---
-| | | | tangle-a | 1 +
+| | | | tangle-a | 1 +
| | | | 1 file changed, 1 insertion(+)
| | | |
| | | | diff --git a/tangle-a b/tangle-a
| |/| |
| | | | side-2
| | | | ---
-| | | | 2 | 1 +
+| | | | 2 | 1 +
| | | | 1 file changed, 1 insertion(+)
| | | |
| | | | diff --git a/2 b/2
| | | |
| | | | side-1
| | | | ---
-| | | | 1 | 1 +
+| | | | 1 | 1 +
| | | | 1 file changed, 1 insertion(+)
| | | |
| | | | diff --git a/1 b/1
| | | |
| | | | Second
| | | | ---
-| | | | one | 1 +
+| | | | one | 1 +
| | | | 1 file changed, 1 insertion(+)
| | | |
| | | | diff --git a/one b/one
|/| |
| | | sixth
| | | ---
-| | | a/two | 1 -
+| | | a/two | 1 -
| | | 1 file changed, 1 deletion(-)
| | |
| | | diff --git a/a/two b/a/two
| | |
| | | fifth
| | | ---
-| | | a/two | 1 +
+| | | a/two | 1 +
| | | 1 file changed, 1 insertion(+)
| | |
| | | diff --git a/a/two b/a/two
| |
| | fourth
| | ---
-| | ein | 1 +
+| | ein | 1 +
| | 1 file changed, 1 insertion(+)
| |
| | diff --git a/ein b/ein
|
| third
| ---
-| ichi | 1 +
-| one | 1 -
+| ichi | 1 +
+| one | 1 -
| 2 files changed, 1 insertion(+), 1 deletion(-)
|
| diff --git a/ichi b/ichi
|
| second
| ---
-| one | 2 +-
+| one | 2 +-
| 1 file changed, 1 insertion(+), 1 deletion(-)
|
| diff --git a/one b/one
initial
---
- one | 1 +
+ one | 1 +
1 file changed, 1 insertion(+)
diff --git a/one b/one
SUBSTFORMAT=%H%n
+check_zip() {
+ zipfile=$1.zip
+ listfile=$1.lst
+ dir=$1
+ dir_with_prefix=$dir/$2
+
+ test_expect_success UNZIP " extract ZIP archive" "
+ (mkdir $dir && cd $dir && $UNZIP ../$zipfile)
+ "
+
+ test_expect_success UNZIP " validate filenames" "
+ (cd ${dir_with_prefix}a && find .) | sort >$listfile &&
+ test_cmp a.lst $listfile
+ "
+
+ test_expect_success UNZIP " validate file contents" "
+ diff -r a ${dir_with_prefix}a
+ "
+}
+
test_expect_success \
'populate workdir' \
'mkdir a b c &&
'git archive vs. git tar-tree' \
'test_cmp b.tar b2.tar'
+test_expect_success 'git archive on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive HEAD >b3.tar &&
+ test_cmp b.tar b3.tar
+'
+
test_expect_success \
'git archive in a bare repo' \
'(cd bare.git && git archive HEAD) >b3.tar'
test_cmp a/substfile2 g/prefix/a/substfile2
'
+$UNZIP -v >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+ say "Skipping ZIP tests, because unzip was not found"
+else
+ test_set_prereq UNZIP
+fi
+
test_expect_success \
'git archive --format=zip' \
'git archive --format=zip HEAD >d.zip'
+check_zip d
+
test_expect_success \
'git archive --format=zip in a bare repo' \
'(cd bare.git && git archive --format=zip HEAD) >d1.zip'
test_cmp b.tar d4.zip
'
-$UNZIP -v >/dev/null 2>&1
-if [ $? -eq 127 ]; then
- say "Skipping ZIP tests, because unzip was not found"
-else
- test_set_prereq UNZIP
-fi
-
-test_expect_success UNZIP \
- 'extract ZIP archive' \
- '(mkdir d && cd d && $UNZIP ../d.zip)'
-
-test_expect_success UNZIP \
- 'validate filenames' \
- '(cd d/a && find .) | sort >d.lst &&
- test_cmp a.lst d.lst'
-
-test_expect_success UNZIP \
- 'validate file contents' \
- 'diff -r a d/a'
-
test_expect_success \
'git archive --format=zip with prefix' \
'git archive --format=zip --prefix=prefix/ HEAD >e.zip'
-test_expect_success UNZIP \
- 'extract ZIP archive with prefix' \
- '(mkdir e && cd e && $UNZIP ../e.zip)'
+check_zip e prefix/
-test_expect_success UNZIP \
- 'validate filenames with prefix' \
- '(cd e/prefix/a && find .) | sort >e.lst &&
- test_cmp a.lst e.lst'
+test_expect_success 'git archive -0 --format=zip on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive -0 --format=zip HEAD >large.zip
+'
-test_expect_success UNZIP \
- 'validate file contents with prefix' \
- 'diff -r a e/prefix/a'
+check_zip large
+
+test_expect_success 'git archive --format=zip on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive --format=zip HEAD >large-compressed.zip
+'
+
+check_zip large-compressed
test_expect_success \
'git archive --list outside of a git repo' \
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
---
- Documentation/git-cvsimport-script.txt | 9 ++++++++-
- git-cvsimport-script | 4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script | 4 ++--
2 files changed, 10 insertions(+), 3 deletions(-)
50452f9c0c2df1f04d83a26266ba704b13861632
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
---
- builtin-mailinfo.c | 2 +-
+ builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
---
- builtin-mailinfo.c | 4 ++--
+ builtin-mailinfo.c | 4 ++--
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3e5fe51..aabfe5c 100644
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
Here is a patch from A U Thor.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
Hope this helps.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
Hopefully this would fix the problem stated there.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
Signed-off-by: David K=E5gedal <davidk@lysator.liu.se>
---
- Documentation/git-cvsimport-script.txt | 9 ++++++++-
- git-cvsimport-script | 4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script | 4 ++--
2 files changed, 10 insertions(+), 3 deletions(-)
50452f9c0c2df1f04d83a26266ba704b13861632
Here is a patch from A U Thor.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
- builtin-mailinfo.c | 2 +-
+ builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
Here comes a commit log message, and
its second line is here.
---
- builtin-mailinfo.c | 4 ++--
+ builtin-mailinfo.c | 4 ++--
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3e5fe51..aabfe5c 100644
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
git request-pull initial "$downstream_url" >../request
) &&
<request sed -nf fuzz.sed >request.fuzzy &&
- test_cmp expect request.fuzzy
+ test_i18ncmp expect request.fuzzy
'
git push parent2 HEAD
'
+# $1 = local revision
+# $2 = remote revision (tested to be equal to the local one)
+check_pushed_commit () {
+ git log -1 --format='%h %s' "$1" >expect &&
+ git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual &&
+ test_cmp expect actual
+}
+
+# $1 = push.default value
+# $2 = expected target branch for the push
+test_push_success () {
+ git -c push.default="$1" push &&
+ check_pushed_commit HEAD "$2"
+}
+
+# $1 = push.default value
+# check that push fails and does not modify any remote branch
+test_push_failure () {
+ git --git-dir=repo1 log --no-walk --format='%h %s' --all >expect &&
+ test_must_fail git -c push.default="$1" push &&
+ git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual &&
+ test_cmp expect actual
+}
+
test_expect_success '"upstream" pushes to configured upstream' '
git checkout master &&
test_config branch.master.remote parent1 &&
test_config branch.master.merge refs/heads/foo &&
- test_config push.default upstream &&
test_commit two &&
- git push &&
- echo two >expect &&
- git --git-dir=repo1 log -1 --format=%s foo >actual &&
- test_cmp expect actual
+ test_push_success upstream foo
'
test_expect_success '"upstream" does not push on unconfigured remote' '
test_unconfig branch.master.remote &&
test_config push.default upstream &&
test_commit three &&
- test_must_fail git push
+ test_push_failure upstream
'
test_expect_success '"upstream" does not push on unconfigured branch' '
test_unconfig branch.master.merge &&
test_config push.default upstream
test_commit four &&
- test_must_fail git push
+ test_push_failure upstream
'
test_expect_success '"upstream" does not push when remotes do not match' '
test_must_fail git push parent2
'
+test_expect_success 'push from/to new branch with upstream, matching and simple' '
+ git checkout -b new-branch &&
+ test_push_failure simple &&
+ test_push_failure matching &&
+ test_push_failure upstream
+'
+
+test_expect_success 'push from/to new branch with current creates remote branch' '
+ test_config branch.new-branch.remote repo1 &&
+ git checkout new-branch &&
+ test_push_success current new-branch
+'
+
+test_expect_success 'push to existing branch, with no upstream configured' '
+ test_config branch.master.remote repo1 &&
+ git checkout master &&
+ test_push_failure simple &&
+ test_push_failure upstream
+'
+
+test_expect_success 'push to existing branch, upstream configured with same name' '
+ test_config branch.master.remote repo1 &&
+ test_config branch.master.merge refs/heads/master &&
+ git checkout master &&
+ test_commit six &&
+ test_push_success upstream master &&
+ test_commit seven &&
+ test_push_success simple master
+'
+
+test_expect_success 'push to existing branch, upstream configured with different name' '
+ test_config branch.master.remote repo1 &&
+ test_config branch.master.merge refs/heads/other-name &&
+ git checkout master &&
+ test_commit eight &&
+ test_push_success upstream other-name &&
+ test_commit nine &&
+ test_push_failure simple &&
+ git --git-dir=repo1 log -1 --format="%h %s" "other-name" >expect-other-name &&
+ test_push_success current master &&
+ git --git-dir=repo1 log -1 --format="%h %s" "other-name" >actual-other-name &&
+ test_cmp expect-other-name actual-other-name
+'
+
test_done
)
'
+test_expect_success 'push unpushed submodules when not needed' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git checkout master &&
+ >junk5 &&
+ git add junk5 &&
+ git commit -m "Fifth junk" &&
+ git push &&
+ git rev-parse origin/master >../../../expected
+ ) &&
+ git checkout master &&
+ git add gar/bage &&
+ git commit -m "Fifth commit for gar/bage" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules when not needed 2' '
+ (
+ cd submodule.git &&
+ git rev-parse master >../expected
+ ) &&
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ >junk6 &&
+ git add junk6 &&
+ git commit -m "Sixth junk"
+ ) &&
+ >junk2 &&
+ git add junk2 &&
+ git commit -m "Second junk for work" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules recursively' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git checkout master &&
+ > junk7 &&
+ git add junk7 &&
+ git commit -m "Seventh junk" &&
+ git rev-parse master >../../../expected
+ ) &&
+ git checkout master &&
+ git add gar/bage &&
+ git commit -m "Seventh commit for gar/bage" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushable submodule recursively fails' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git rev-parse origin/master >../../../expected &&
+ git checkout master~0 &&
+ > junk8 &&
+ git add junk8 &&
+ git commit -m "Eighth junk"
+ ) &&
+ git add gar/bage &&
+ git commit -m "Eighth commit for gar/bage" &&
+ test_must_fail git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
test_done
esac
done
- if test $# -ne 3
- then
- error "invalid number of arguments"
- fi
-
+ msg=$1
+ shift
cmd=$1
- repo=$2
- msg=$3
+ shift
+ repo=$1
+ shift || error "invalid number of arguments"
if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo"
then
fi
fi
- test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" 2>output &&
+ test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" "$@" 2>output &&
echo "fatal: remote error: $msg: /$repo" >expect &&
test_cmp expect output
ret=$?
}
msg="access denied or repository not exported"
-test_expect_success 'clone non-existent' "test_remote_error clone nowhere.git '$msg'"
-test_expect_success 'push disabled' "test_remote_error push repo.git '$msg'"
-test_expect_success 'read access denied' "test_remote_error -x fetch repo.git '$msg'"
-test_expect_success 'not exported' "test_remote_error -n fetch repo.git '$msg'"
+test_expect_success 'clone non-existent' "test_remote_error '$msg' clone nowhere.git "
+test_expect_success 'push disabled' "test_remote_error '$msg' push repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x '$msg' fetch repo.git "
+test_expect_success 'not exported' "test_remote_error -n '$msg' fetch repo.git "
stop_git_daemon
start_git_daemon --informative-errors
-test_expect_success 'clone non-existent' "test_remote_error clone nowhere.git 'no such repository'"
-test_expect_success 'push disabled' "test_remote_error push repo.git 'service not enabled'"
-test_expect_success 'read access denied' "test_remote_error -x fetch repo.git 'no such repository'"
-test_expect_success 'not exported' "test_remote_error -n fetch repo.git 'repository not exported'"
+test_expect_success 'clone non-existent' "test_remote_error 'no such repository' clone nowhere.git "
+test_expect_success 'push disabled' "test_remote_error 'service not enabled' push repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x 'no such repository' fetch repo.git "
+test_expect_success 'not exported' "test_remote_error -n 'repository not exported' fetch repo.git "
stop_git_daemon
test_done
(
cd test && git checkout b1
) >actual &&
- grep "have 1 and 1 different" actual
+ test_i18ngrep "have 1 and 1 different" actual
'
test_expect_success 'checkout with local tracked branch' '
git checkout master &&
git checkout follower >actual &&
- grep "is ahead of" actual
+ test_i18ngrep "is ahead of" actual
'
test_expect_success 'status' '
# reports nothing to commit
test_must_fail git commit --dry-run
) >actual &&
- grep "have 1 and 1 different" actual
+ test_i18ngrep "have 1 and 1 different" actual
'
test_expect_success 'fail to track lightweight tags' '
git checkout master &&
git tag light &&
test_must_fail git branch --track lighttrack light >actual &&
- test_must_fail grep "set up to track" actual &&
+ test_i18ngrep ! "set up to track" actual &&
test_must_fail git checkout lighttrack
'
git checkout master &&
git tag -m heavy heavy &&
test_must_fail git branch --track heavytrack heavy >actual &&
- test_must_fail grep "set up to track" actual &&
+ test_i18ngrep ! "set up to track" actual &&
test_must_fail git checkout heavytrack
'
echo "l3" >two &&
test_tick &&
- git commit -a -m "Left #3" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #3" &&
echo "l4" >two &&
test_tick &&
- git commit -a -m "Left #4" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #4" &&
echo "l5" >two &&
test_tick &&
- git commit -a -m "Left #5" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #5" &&
git tag tag-l5 &&
git checkout right &&
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left:
Left #5
Left #4
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left: (5 commits)
Left #5
Left #4
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left:
Left #5
Left #4
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left: (5 commits)
Left #5
Left #4
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left:
Left #5
Left #4
cat >expected.log <<-EOF &&
Sync with left
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* ${apos}left${apos} of $(pwd):
Left #5
Left #4
cat >expected <<-EOF
Merge branches ${apos}left${apos} and ${apos}right${apos}
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left:
Left #5
Left #4
Common #2
Common #1
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* tag ${apos}tag-l5${apos}:
Left #5
Left #4
Common #2
Common #1
+ By Another Author (3) and A U Thor (2)
+ via Another Committer
* left:
Left #5
Left #4
test_cmp expect actual
'
+test_expect_success 'listing tags in column' '
+ COLUMNS=40 git tag -l --column=row >actual &&
+ cat >expected <<\EOF &&
+a1 aa1 cba t210 t211
+v0.2.1 v1.0 v1.0.1 v1.1.3
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'listing tags in column with column.*' '
+ git config column.tag row &&
+ git config column.ui dense &&
+ COLUMNS=40 git tag -l >actual &&
+ git config --unset column.ui &&
+ git config --unset column.tag &&
+ cat >expected <<\EOF &&
+a1 aa1 cba t210 t211
+v0.2.1 v1.0 v1.0.1 v1.1.3
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'listing tag with -n --column should fail' '
+ test_must_fail git tag --column -n
+'
+
+test_expect_success 'listing tags -n in column with column.ui ignored' '
+ git config column.ui "row dense" &&
+ COLUMNS=40 git tag -l -n >actual &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+a1 Foo
+aa1 Foo
+cba Foo
+t210 Foo
+t211 Foo
+v0.2.1 Foo
+v1.0 Foo
+v1.0.1 Foo
+v1.1.3 Foo
+EOF
+ test_cmp expected actual
+'
+
# creating and verifying lightweight tags:
test_expect_success \
test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
'
+test_expect_success 'status --column' '
+ COLUMNS=50 git status --column="column dense" >output &&
+ cat >expect <<\EOF &&
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: dir2/added
+#
+# Changes not staged for commit:
+# (use "git add <file>..." to update what will be committed)
+# (use "git checkout -- <file>..." to discard changes in working directory)
+#
+# modified: dir1/modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# dir1/untracked dir2/untracked untracked
+# dir2/modified output
+EOF
+ test_cmp expect output
+'
+
cat >expect <<\EOF
# On branch master
# Changes to be committed:
Trying simple merge with c3
Trying simple merge with c4
Merge made by the 'octopus' strategy.
- c2.c | 1 +
- c3.c | 1 +
- c4.c | 1 +
+ c2.c | 1 +
+ c3.c | 1 +
+ c4.c | 1 +
3 files changed, 3 insertions(+)
create mode 100644 c2.c
create mode 100644 c3.c
test_expect_success 'merge output uses pretty names' '
git reset --hard c1 &&
git merge c2 c3 c4 >actual &&
- test_cmp actual expected
+ test_i18ncmp expected actual
'
cat >expected <<\EOF
Merge made by the 'recursive' strategy.
- c5.c | 1 +
+ c5.c | 1 +
1 file changed, 1 insertion(+)
create mode 100644 c5.c
EOF
test_expect_success 'merge reduces irrelevant remote heads' '
GIT_MERGE_VERBOSITY=0 git merge c4 c5 >actual &&
- test_cmp expected actual
+ test_i18ncmp expected actual
'
cat >expected <<\EOF
Fast-forwarding to: c1
Trying simple merge with c2
Merge made by the 'octopus' strategy.
- c1.c | 1 +
- c2.c | 1 +
+ c1.c | 1 +
+ c2.c | 1 +
2 files changed, 2 insertions(+)
create mode 100644 c1.c
create mode 100644 c2.c
test_expect_success 'merge fast-forward output uses pretty names' '
git reset --hard c0 &&
git merge c1 c2 >actual &&
- test_cmp actual expected
+ test_i18ncmp expected actual
'
test_done
test_cmp important c1.c
'
+test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+ git reset --hard c1 &&
+ git mv c1.c other.c &&
+ git commit -m rename &&
+ cp important other.c &&
+ git merge c1a &&
+ test_cmp important other.c
+'
+
test_expect_success 'will not overwrite untracked subtree' '
git reset --hard c0 &&
rm -rf sub &&
test "$diff" = ""
'
+test_expect_success PERL 'difftool forwards arguments to diff' '
+ >for-diff &&
+ git add for-diff &&
+ echo changes>for-diff &&
+ git add for-diff &&
+ diff=$(git difftool --cached --no-prompt -- for-diff) &&
+ test "$diff" = "" &&
+ git reset -- for-diff &&
+ rm for-diff
+'
+
test_expect_success PERL 'difftool honors --gui' '
git config merge.tool bogus-tool &&
git config diff.tool bogus-tool &&
restore_test_defaults
'
+test_expect_success PERL 'difftool --gui last setting wins' '
+ git config diff.guitool bogus-tool &&
+ git difftool --no-prompt --gui --no-gui &&
+
+ git config merge.tool bogus-tool &&
+ git config diff.tool bogus-tool &&
+ git config diff.guitool test-tool &&
+ diff=$(git difftool --no-prompt --no-gui --gui branch) &&
+ test "$diff" = "branch" &&
+
+ restore_test_defaults
+'
+
test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
git config diff.tool test-tool &&
echo "$diff" | stdin_doesnot_contain br2
'
+test_expect_success PERL 'difftool --tool-help' '
+ tool_help=$(git difftool --tool-help) &&
+ echo "$tool_help" | stdin_contains tool
+'
+
+test_expect_success PERL 'setup change in subdirectory' '
+ git checkout master &&
+ mkdir sub &&
+ echo master >sub/sub &&
+ git add sub/sub &&
+ git commit -m "added sub/sub" &&
+ echo test >>file &&
+ echo test >>sub/sub &&
+ git add . &&
+ git commit -m "modified both"
+'
+
+test_expect_success PERL 'difftool -d' '
+ diff=$(git difftool -d --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff' '
+ diff=$(git difftool --dir-diff --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff ignores --prompt' '
+ diff=$(git difftool --dir-diff --prompt --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff from subdirectory' '
+ (
+ cd sub &&
+ diff=$(git difftool --dir-diff --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+ )
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git column'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cat >lista <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+'
+
+test_expect_success 'never' '
+ git column --indent=Z --mode=never <lista >actual &&
+ test_cmp lista actual
+'
+
+test_expect_success 'always' '
+ cat >expected <<\EOF &&
+Zone
+Ztwo
+Zthree
+Zfour
+Zfive
+Zsix
+Zseven
+Zeight
+Znine
+Zten
+Zeleven
+EOF
+ git column --indent=Z --mode=plain <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '80 columns' '
+ cat >expected <<\EOF &&
+one two three four five six seven eight nine ten eleven
+EOF
+ COLUMNS=80 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+
+test_expect_success COLUMNS_CAN_BE_1 'COLUMNS = 1' '
+ COLUMNS=1 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+ git column --mode=column --width=1 <lista >actual &&
+ test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, nodense' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column,nodense < lista > actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, dense' '
+ cat >expected <<\EOF &&
+one five nine
+two six ten
+three seven eleven
+four eight
+EOF
+ git column --mode=column,dense < lista > actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column --padding 2 <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+ cat >expected <<\EOF &&
+ one seven
+ two eight
+ three nine
+ four ten
+ five eleven
+ six
+EOF
+ git column --mode=column --indent=" " <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, nodense' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row,nodense <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, dense' '
+ cat >expected <<\EOF &&
+one two three
+four five six
+seven eight nine
+ten eleven
+EOF
+ git column --mode=row,dense <lista >actual &&
+ test_cmp expected actual
+'
+
+test_done
test_expect_success 'snapshots: bad tree-ish id (tagged object)' '
echo object > tag-object &&
git add tag-object &&
- git commit -m "Object to be tagged" &&
+ test_tick && git commit -m "Object to be tagged" &&
git tag tagged-object `git hash-object tag-object` &&
gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" &&
grep "400 - Object is not a tree-ish" gitweb.output
'
test_debug 'cat gitweb.output'
+# ----------------------------------------------------------------------
+# modification times (Last-Modified and If-Modified-Since)
+
+test_expect_success 'modification: feed last-modified' '
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: feed if-modified-since (modified)' '
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: feed if-modified-since (unmodified)' '
+ export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot last-modified' '
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot if-modified-since (modified)' '
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot if-modified-since (unmodified)' '
+ export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: tree snapshot' '
+ ID=`git rev-parse --verify HEAD^{tree}` &&
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ ! grep -i "last-modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
# ----------------------------------------------------------------------
# load checking
#!/bin/sh
-test_description='git-p4 tests'
+test_description='git p4 tests'
. ./lib-git-p4.sh
)
'
-test_expect_success 'basic git-p4 clone' '
- "$GITP4" clone --dest="$git" //depot &&
+test_expect_success 'basic git p4 clone' '
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
)
'
-test_expect_success 'git-p4 clone @all' '
- "$GITP4" clone --dest="$git" //depot@all &&
+test_expect_success 'git p4 clone @all' '
+ git p4 clone --dest="$git" //depot@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
)
'
-test_expect_success 'git-p4 sync uninitialized repo' '
+test_expect_success 'git p4 sync uninitialized repo' '
test_create_repo "$git" &&
test_when_finished cleanup_git &&
(
cd "$git" &&
- test_must_fail "$GITP4" sync
+ test_must_fail git p4 sync
)
'
# Create a git repo by hand. Add a commit so that HEAD is valid.
# Test imports a new p4 repository into a new git branch.
#
-test_expect_success 'git-p4 sync new branch' '
+test_expect_success 'git p4 sync new branch' '
test_create_repo "$git" &&
test_when_finished cleanup_git &&
(
cd "$git" &&
test_commit head &&
- "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
+ git p4 sync --branch=refs/remotes/p4/depot //depot@all &&
git log --oneline p4/depot >lines &&
test_line_count = 2 lines
)
p4 add sub2/f2 &&
p4 submit -d "sub2/f2"
) &&
- "$GITP4" clone --dest="$git" //depot/sub1 //depot/sub2 &&
+ git p4 clone --dest="$git" //depot/sub1 //depot/sub2 &&
test_when_finished cleanup_git &&
(
cd "$git" &&
p4 add sub1/f3 &&
p4 submit -d "sub1/f3"
) &&
- "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+ git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
p4 add sub2/f3 &&
p4 submit -d "sub2/f3"
) &&
- "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+ git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
exit 1
EOF
chmod 755 "$badp4dir"/p4 &&
- PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+ PATH="$badp4dir:$PATH" git p4 clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
test $retval -eq 1 &&
test_must_fail grep -q Traceback errs
'
)
'
-test_expect_success 'wildcard files git-p4 clone' '
- "$GITP4" clone --dest="$git" //depot &&
+test_expect_success 'wildcard files git p4 clone' '
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
)
'
+test_expect_success 'wildcard files submit back to p4, add' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo git-wild-hash >git-wild#hash &&
+ echo git-wild-star >git-wild\*star &&
+ echo git-wild-at >git-wild@at &&
+ echo git-wild-percent >git-wild%percent &&
+ git add git-wild* &&
+ git commit -m "add some wildcard filenames" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file git-wild#hash &&
+ test_path_is_file git-wild\*star &&
+ test_path_is_file git-wild@at &&
+ test_path_is_file git-wild%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, modify' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo new-line >>git-wild#hash &&
+ echo new-line >>git-wild\*star &&
+ echo new-line >>git-wild@at &&
+ echo new-line >>git-wild%percent &&
+ git add git-wild* &&
+ git commit -m "modify the wildcard files" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_line_count = 2 git-wild#hash &&
+ test_line_count = 2 git-wild\*star &&
+ test_line_count = 2 git-wild@at &&
+ test_line_count = 2 git-wild%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, copy' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ cp file2 git-wild-cp#hash &&
+ git add git-wild-cp#hash &&
+ cp git-wild\*star file-wild-3 &&
+ git add file-wild-3 &&
+ git commit -m "wildcard copies" &&
+ git config git-p4.detectCopies true &&
+ git config git-p4.detectCopiesHarder true &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file git-wild-cp#hash &&
+ test_path_is_file file-wild-3
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, rename' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git mv git-wild@at file-wild-4 &&
+ git mv file-wild-3 git-wild-cp%percent &&
+ git commit -m "wildcard renames" &&
+ git config git-p4.detectRenames true &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing git-wild@at &&
+ test_path_is_file git-wild-cp%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, delete' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git rm git-wild* &&
+ git commit -m "delete the wildcard files" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing git-wild#hash &&
+ test_path_is_missing git-wild\*star &&
+ test_path_is_missing git-wild@at &&
+ test_path_is_missing git-wild%percent
+ )
+'
+
test_expect_success 'clone bare' '
- "$GITP4" clone --dest="$git" --bare //depot &&
+ git p4 clone --dest="$git" --bare //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
p4_add_user alice Alice &&
p4_add_user bob Bob &&
p4_grant_admin alice &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
git config git-p4.skipSubmitEditCheck true &&
- P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret git p4 commit --preserve-user &&
p4_check_commit_author file1 alice &&
p4_check_commit_author file2 bob
)
# Test username support, submitting as bob, who lacks admin rights. Should
# not submit change to p4 (git diff should show deltas).
test_expect_success 'refuse to preserve users without perms' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
- test_must_fail "$GITP4" commit --preserve-user &&
+ test_must_fail git p4 commit --preserve-user &&
! git diff --exit-code HEAD..p4/master
)
'
# What happens with unknown author? Without allowMissingP4Users it should fail.
test_expect_success 'preserve user where author is unknown to p4' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
- test_must_fail "$GITP4" commit --preserve-user &&
+ test_must_fail git p4 commit --preserve-user &&
! git diff --exit-code HEAD..p4/master &&
echo "$0: repeat with allowMissingP4Users enabled" &&
git config git-p4.allowMissingP4Users true &&
git config git-p4.preserveUser true &&
- "$GITP4" commit &&
+ git p4 commit &&
git diff --exit-code HEAD..p4/master &&
p4_check_commit_author file1 alice
)
'
-# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
+# If we're *not* using --preserve-user, git p4 should warn if we're submitting
# changes that are not all ours.
# Test: user in p4 and user unknown to p4.
# Test: warning disabled and user is the same.
test_expect_success 'not preserving user with mixed authorship' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
make_change_by_user usernamefile3 Derek derek@localhost &&
P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
- "$GITP4" commit |\
+ git p4 commit |\
grep "git author derek@localhost does not match" &&
make_change_by_user usernamefile3 Charlie charlie@localhost &&
- "$GITP4" commit |\
+ git p4 commit |\
grep "git author charlie@localhost does not match" &&
make_change_by_user usernamefile3 alice alice@localhost &&
- "$GITP4" commit |\
+ git p4 commit |\
test_must_fail grep "git author.*does not match" &&
git config git-p4.skipUserNameCheck true &&
make_change_by_user usernamefile3 Charlie charlie@localhost &&
- "$GITP4" commit |\
+ git p4 commit |\
test_must_fail grep "git author.*does not match" &&
p4_check_commit_author usernamefile3 alice
p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
sleep 3 &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
# Repeat, this time with a smaller threshold and confirm that the rename is
# detected in P4.
test_expect_success 'detect renames' '
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
- git config git-p4.skipSubmitEditCheck true &&
+ git config git-p4.skipSubmitEdit true &&
git mv file1 file4 &&
git commit -a -m "Rename file1 to file4" &&
git diff-tree -r -M HEAD &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file4 &&
p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
git commit -a -m "Rename file4 to file5" &&
git diff-tree -r -M HEAD &&
git config git-p4.detectRenames true &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file5 &&
p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
git config git-p4.detectRenames $(($level + 2)) &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file6 &&
p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
git config git-p4.detectRenames $(($level - 2)) &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file7 &&
p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
)
# Modify and copy a file, configure a smaller threshold in detectCopies and
# confirm that copy is detected in P4.
test_expect_success 'detect copies' '
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
- git config git-p4.skipSubmitEditCheck true &&
+ git config git-p4.skipSubmitEdit true &&
cp file2 file8 &&
git add file8 &&
git commit -a -m "Copy file2 to file8" &&
git diff-tree -r -C HEAD &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file8 &&
p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
git commit -a -m "Copy file2 to file9" &&
git diff-tree -r -C HEAD &&
git config git-p4.detectCopies true &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file9 &&
p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
git add file2 file10 &&
git commit -a -m "Modify and copy file2 to file10" &&
git diff-tree -r -C HEAD &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file10 &&
p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
test "$src" = file10 &&
git config git-p4.detectCopiesHarder true &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file11 &&
p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
test "$src" = file10 &&
git config git-p4.detectCopies $(($level + 2)) &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file12 &&
p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
test "$src" = file10 &&
git config git-p4.detectCopies $(($level - 2)) &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 filelog //depot/file13 &&
p4 filelog //depot/file13 | grep -q "branch from //depot/file"
)
#!/bin/sh
-test_description='git-p4 p4 branching tests'
+test_description='git p4 tests for p4 branches'
. ./lib-git-p4.sh
test_expect_success 'import main, no branch detection' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot/main@all &&
+ git p4 clone --dest="$git" //depot/main@all &&
(
cd "$git" &&
git log --oneline --graph --decorate --all &&
test_expect_success 'import branch1, no branch detection' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot/branch1@all &&
+ git p4 clone --dest="$git" //depot/branch1@all &&
(
cd "$git" &&
git log --oneline --graph --decorate --all &&
test_expect_success 'import branch2, no branch detection' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot/branch2@all &&
+ git p4 clone --dest="$git" //depot/branch2@all &&
(
cd "$git" &&
git log --oneline --graph --decorate --all &&
test_expect_success 'import depot, no branch detection' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
(
cd "$git" &&
git log --oneline --graph --decorate --all &&
test_expect_success 'import depot, branch detection' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" --detect-branches //depot@all &&
+ git p4 clone --dest="$git" --detect-branches //depot@all &&
(
cd "$git" &&
(
cd "$git" &&
git config git-p4.branchList main:branch1 &&
- "$GITP4" clone --dest=. --detect-branches //depot@all &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
git log --oneline --graph --decorate --all &&
# Configure branches through git-config and clone them.
# All files are tested to make sure branches were cloned correctly.
# Finally, make an update to branch1 on P4 side to check if it is imported
-# correctly by git-p4.
-test_expect_success 'git-p4 clone simple branches' '
+# correctly by git p4.
+test_expect_success 'git p4 clone simple branches' '
test_when_finished cleanup_git &&
test_create_repo "$git" &&
(
cd "$git" &&
git config git-p4.branchList branch1:branch2 &&
git config --add git-p4.branchList branch1:branch3 &&
- "$GITP4" clone --dest=. --detect-branches //depot@all &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
git log --all --graph --decorate --stat &&
git reset --hard p4/depot/branch1 &&
test -f file1 &&
p4 submit -d "update file2 in branch3" &&
cd "$git" &&
git reset --hard p4/depot/branch1 &&
- "$GITP4" rebase &&
+ git p4 rebase &&
grep file2_ file2
)
'
# Create a complex branch structure in P4 depot to check if they are correctly
-# cloned. The branches are created from older changelists to check if git-p4 is
+# cloned. The branches are created from older changelists to check if git p4 is
# able to correctly detect them.
# The final expected structure is:
# `branch1
# `- file1
# `- file2
# `- file3
-test_expect_success 'git-p4 add complex branches' '
+test_expect_success 'git p4 add complex branches' '
test_when_finished cleanup_git &&
test_create_repo "$git" &&
(
)
'
-# Configure branches through git-config and clone them. git-p4 will only be able
+# Configure branches through git-config and clone them. git p4 will only be able
# to clone the original structure if it is able to detect the origin changelist
# of each branch.
-test_expect_success 'git-p4 clone complex branches' '
+test_expect_success 'git p4 clone complex branches' '
test_when_finished cleanup_git &&
test_create_repo "$git" &&
(
git config --add git-p4.branchList branch1:branch3 &&
git config --add git-p4.branchList branch1:branch4 &&
git config --add git-p4.branchList branch1:branch5 &&
- "$GITP4" clone --dest=. --detect-branches //depot@all &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
git log --all --graph --decorate --stat &&
git reset --hard p4/depot/branch1 &&
test_path_is_file file1 &&
#!/bin/sh
-test_description='git-p4 p4 filetype tests'
+test_description='git p4 filetype tests'
. ./lib-git-p4.sh
test_expect_success 'utf-16 file test' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
(
cd "$git" &&
build_smush &&
test_when_finished rm -f k_smush.py ko_smush.py &&
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
(
cd "$git" &&
"$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush &&
test_cmp cli-k-text-ko-smush k-text-ko &&
- # utf16, even though p4 expands keywords, git-p4 does not
+ # utf16, even though p4 expands keywords, git p4 does not
# try to undo that
test_cmp "$cli/k-utf16-k" k-utf16-k &&
test_cmp "$cli/k-utf16-ko" k-utf16-ko
p4 submit -d appledouble
) &&
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot@all &&
+ git p4 clone --dest="$git" //depot@all &&
(
cd "$git" &&
test ! -f double.png
#!/bin/sh
-test_description='git-p4 transparency to shell metachars in filenames'
+test_description='git p4 transparency to shell metachars in filenames'
. ./lib-git-p4.sh
'
test_expect_success 'shell metachars in filenames' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
echo f2 >"file with spaces" &&
git add "file with spaces" &&
git commit -m "add files" &&
- P4EDITOR=touch "$GITP4" submit
+ P4EDITOR=touch git p4 submit
) &&
(
cd "$cli" &&
'
test_expect_success 'deleting with shell metachars' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git rm foo\$bar &&
git rm file\ with\ spaces &&
git commit -m "remove files" &&
- P4EDITOR=touch "$GITP4" submit
+ P4EDITOR=touch git p4 submit
) &&
(
cd "$cli" &&
cd "$git" &&
git config git-p4.branchList main:branch\$3 &&
- "$GITP4" clone --dest=. --detect-branches //depot@all &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
git log --all --graph --decorate --stat &&
git reset --hard p4/depot/branch\$3 &&
test -f shell_char_branch_file &&
#!/bin/sh
-test_description='git-p4 p4 label tests'
+test_description='git p4 label tests'
. ./lib-git-p4.sh
p4 labels ... &&
- "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+ git p4 clone --dest="$git" --detect-labels //depot@all &&
cd "$git" &&
git tag &&
p4 labels ... &&
- "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+ git p4 clone --dest="$git" --detect-labels //depot@all &&
cd "$git" &&
git tag | grep tag_f1 &&
#!/bin/sh
-test_description='git-p4 skipSubmitEdit config variables'
+test_description='git p4 skipSubmitEdit config variables'
. ./lib-git-p4.sh
# this works because EDITOR is set to :
test_expect_success 'no config, unedited, say yes' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
echo line >>file1 &&
git commit -a -m "change 2" &&
- echo y | "$GITP4" submit &&
+ echo y | git p4 submit &&
p4 changes //depot/... >wc &&
test_line_count = 2 wc
)
'
test_expect_success 'no config, unedited, say no' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
echo line >>file1 &&
git commit -a -m "change 3 (not really)" &&
- printf "bad response\nn\n" | "$GITP4" submit &&
+ printf "bad response\nn\n" | git p4 submit &&
p4 changes //depot/... >wc &&
test_line_count = 2 wc
)
'
test_expect_success 'skipSubmitEdit' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git config core.editor /bin/false &&
echo line >>file1 &&
git commit -a -m "change 3" &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 changes //depot/... >wc &&
test_line_count = 3 wc
)
'
test_expect_success 'skipSubmitEditCheck' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git config git-p4.skipSubmitEditCheck true &&
echo line >>file1 &&
git commit -a -m "change 4" &&
- "$GITP4" submit &&
+ git p4 submit &&
p4 changes //depot/... >wc &&
test_line_count = 4 wc
)
# check the normal case, where the template really is edited
test_expect_success 'no config, edited' '
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
ed="$TRASH_DIRECTORY/ed.sh" &&
test_when_finished "rm \"$ed\"" &&
cd "$git" &&
echo line >>file1 &&
git commit -a -m "change 5" &&
- EDITOR="\"$ed\"" "$GITP4" submit &&
+ P4EDITOR="" EDITOR="\"$ed\"" git p4 submit &&
p4 changes //depot/... >wc &&
test_line_count = 5 wc
)
#!/bin/sh
-test_description='git-p4 options'
+test_description='git p4 options'
. ./lib-git-p4.sh
'
test_expect_success 'clone no --git-dir' '
- test_must_fail "$GITP4" clone --git-dir=xx //depot
+ test_must_fail git p4 clone --git-dir=xx //depot
'
test_expect_success 'clone --branch' '
- "$GITP4" clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
+ git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
cf="$TRASH_DIRECTORY/cf" &&
test_when_finished "rm \"$cf\"" &&
printf "1\n3\n" >"$cf" &&
- "$GITP4" clone --changesfile="$cf" --dest="$git" //depot &&
+ git p4 clone --changesfile="$cf" --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
cf="$TRASH_DIRECTORY/cf" &&
test_when_finished "rm \"$cf\"" &&
printf "1\n3\n" >"$cf" &&
- test_must_fail "$GITP4" clone --changesfile="$cf" --dest="$git" //depot@all
+ test_must_fail git p4 clone --changesfile="$cf" --dest="$git" //depot@all
'
# imports both master and p4/master in refs/heads
# requires --import-local on sync to find p4 refs/heads
# does not update master on sync, just p4/master
test_expect_success 'clone/sync --import-local' '
- "$GITP4" clone --import-local --dest="$git" //depot@1,2 &&
+ git p4 clone --import-local --dest="$git" //depot@1,2 &&
test_when_finished cleanup_git &&
(
cd "$git" &&
test_line_count = 2 lines &&
git log --oneline refs/heads/p4/master >lines &&
test_line_count = 2 lines &&
- test_must_fail "$GITP4" sync &&
+ test_must_fail git p4 sync &&
- "$GITP4" sync --import-local &&
+ git p4 sync --import-local &&
git log --oneline refs/heads/master >lines &&
test_line_count = 2 lines &&
git log --oneline refs/heads/p4/master >lines &&
'
test_expect_success 'clone --max-changes' '
- "$GITP4" clone --dest="$git" --max-changes 2 //depot@all &&
+ git p4 clone --dest="$git" --max-changes 2 //depot@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
p4 add sub/dir/f4 &&
p4 submit -d "change 4"
) &&
- "$GITP4" clone --dest="$git" --keep-path //depot/sub/dir@all &&
+ git p4 clone --dest="$git" --keep-path //depot/sub/dir@all &&
test_when_finished cleanup_git &&
(
cd "$git" &&
test_path_is_file sub/dir/f4
) &&
cleanup_git &&
- "$GITP4" clone --dest="$git" //depot/sub/dir@all &&
+ git p4 clone --dest="$git" //depot/sub/dir@all &&
(
cd "$git" &&
test_path_is_file f4 &&
(
# big usage message
exec >/dev/null &&
- test_must_fail "$GITP4" clone --dest="$git" --use-client-spec
+ test_must_fail git p4 clone --dest="$git" --use-client-spec
) &&
cli2="$TRASH_DIRECTORY/cli2" &&
mkdir -p "$cli2" &&
) &&
P4CLIENT=client2 &&
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" --use-client-spec //depot/... &&
+ git p4 clone --dest="$git" --use-client-spec //depot/... &&
(
cd "$git" &&
test_path_is_file bus/dir/f4 &&
cd "$git" &&
git init &&
git config git-p4.useClientSpec true &&
- "$GITP4" sync //depot/... &&
+ git p4 sync //depot/... &&
git checkout -b master p4/master &&
test_path_is_file bus/dir/f4 &&
test_path_is_missing file1
#!/bin/sh
-test_description='git-p4 submit'
+test_description='git p4 submit'
. ./lib-git-p4.sh
test_expect_success 'submit with no client dir' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
echo file2 >file2 &&
git commit -m "git commit 2" &&
rm -rf "$cli" &&
git config git-p4.skipSubmitEdit true &&
- "$GITP4" submit
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file1 &&
+ test_path_is_file file2
)
'
# make two commits, but tell it to apply only from HEAD^
test_expect_success 'submit --origin' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
test_commit "file3" &&
test_commit "file4" &&
git config git-p4.skipSubmitEdit true &&
- "$GITP4" submit --origin=HEAD^
+ git p4 submit --origin=HEAD^
) &&
(
cd "$cli" &&
- p4 sync &&
test_path_is_missing "file3.t" &&
test_path_is_file "file4.t"
)
test_expect_success 'submit with allowSubmit' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
test_commit "file5" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.allowSubmit "nobranch" &&
- test_must_fail "$GITP4" submit &&
+ test_must_fail git p4 submit &&
git config git-p4.allowSubmit "nobranch,master" &&
- "$GITP4" submit
+ git p4 submit
)
'
test_expect_success 'submit with master branch name from argv' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
test_commit "file6" &&
git config git-p4.skipSubmitEdit true &&
- test_must_fail "$GITP4" submit nobranch &&
+ test_must_fail git p4 submit nobranch &&
git branch otherbranch &&
git reset --hard HEAD^ &&
test_commit "file7" &&
- "$GITP4" submit otherbranch
+ git p4 submit otherbranch
) &&
(
cd "$cli" &&
- p4 sync &&
test_path_is_file "file6.t" &&
test_path_is_missing "file7.t"
)
'
+#
+# Basic submit tests, the five handled cases
+#
+
+test_expect_success 'submit modify' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo line >>file1 &&
+ git add file1 &&
+ git commit -m file1 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file1 &&
+ test_line_count = 2 file1
+ )
+'
+
+test_expect_success 'submit add' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo file13 >file13 &&
+ git add file13 &&
+ git commit -m file13 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file13
+ )
+'
+
+test_expect_success 'submit delete' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git rm file4.t &&
+ git commit -m "delete file4.t" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing file4.t
+ )
+'
+
+test_expect_success 'submit copy' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectCopies true &&
+ git config git-p4.detectCopiesHarder true &&
+ cp file5.t file5.ta &&
+ git add file5.ta &&
+ git commit -m "copy to file5.ta" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file5.ta &&
+ test ! -w file5.ta
+ )
+'
+
+test_expect_success 'submit rename' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectRenames true &&
+ git mv file6.t file6.ta &&
+ git commit -m "rename file6.t to file6.ta" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing file6.t &&
+ test_path_is_file file6.ta &&
+ test ! -w file6.ta
+ )
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
#!/bin/sh
-test_description='git-p4 relative chdir'
+test_description='git p4 relative chdir'
. ./lib-git-p4.sh
(
P4CONFIG=p4config && export P4CONFIG &&
sane_unset P4PORT P4CLIENT &&
- "$GITP4" clone --verbose --dest="$git" //depot
+ git p4 clone --verbose --dest="$git" //depot
)
'
(
P4CONFIG=p4config && export P4CONFIG &&
sane_unset P4PORT P4CLIENT &&
- "$GITP4" clone --verbose --dest="git" //depot
+ git p4 clone --verbose --dest="git" //depot
)
'
#!/bin/sh
-test_description='git-p4 client view'
+test_description='git p4 client view'
. ./lib-git-p4.sh
test_expect_success 'unsupported view wildcard %%n' '
client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
test_when_finished cleanup_git &&
- test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
'
test_expect_success 'unsupported view wildcard *' '
client_view "//depot/*/bar/... //client/*/bar/..." &&
test_when_finished cleanup_git &&
- test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
'
test_expect_success 'wildcard ... only supported at end of spec 1' '
client_view "//depot/.../file11 //client/.../file11" &&
test_when_finished cleanup_git &&
- test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
'
test_expect_success 'wildcard ... only supported at end of spec 2' '
client_view "//depot/.../a/... //client/.../a/..." &&
test_when_finished cleanup_git &&
- test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
'
test_expect_success 'basic map' '
files="cli1/file11 cli1/file12" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
client_view &&
client_verify &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify
'
files="file11" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
cli2/dir2/file21 cli2/dir2/file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="file21 file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="cli12/file21 cli12/file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
"-//depot/dir2/... //client/..." &&
client_verify &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify
'
files="dir1/file11 dir1/file12" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="dir1/file11 dir1/file12 dir2/file21" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="cli/file11 cli/file12 cli/file21 cli/file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="cli/file11 cli/file12 cli/file21" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
client_view "//depot/dir1/... \"//client/cdir 1/...\"" &&
client_verify "cdir 1/file11" "cdir 1/file12" &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify "cdir 1/file11" "cdir 1/file12"
'
test_expect_success 'clone --use-client-spec sets useClientSpec' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
(
cd "$git" &&
git config --bool git-p4.useClientSpec >actual &&
files="dir1/file11 dir1/file12 dir2/file21 dir2/file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
git_verify dir1/file11 dir1/file12
'
test_expect_success 'subdir clone, submit modify' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
echo line >>dir1/file12 &&
git add dir1/file12 &&
git commit -m dir1/file12 &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
test_expect_success 'subdir clone, submit add' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
echo file13 >dir1/file13 &&
git add dir1/file13 &&
git commit -m dir1/file13 &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
test_expect_success 'subdir clone, submit delete' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git rm dir1/file12 &&
git commit -m "delete dir1/file12" &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
test_expect_success 'subdir clone, submit copy' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
cp dir1/file11 dir1/file11a &&
git add dir1/file11a &&
git commit -m "copy to dir1/file11a" &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
- test_path_is_file dir1/file11a
+ test_path_is_file dir1/file11a &&
+ test ! -w dir1/file11a
)
'
test_expect_success 'subdir clone, submit rename' '
client_view "//depot/... //client/..." &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.detectRenames true &&
git mv dir1/file13 dir1/file13a &&
git commit -m "rename dir1/file13 to dir1/file13a" &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
test_path_is_missing dir1/file13 &&
- test_path_is_file dir1/file13a
+ test_path_is_file dir1/file13a &&
+ test ! -w dir1/file13a
+ )
+'
+
+# see t9800 for the non-client-spec case, and the rest of the wildcard tests
+test_expect_success 'wildcard files submit back to p4, client-spec case' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ echo git-wild-hash >dir1/git-wild#hash &&
+ echo git-wild-star >dir1/git-wild\*star &&
+ echo git-wild-at >dir1/git-wild@at &&
+ echo git-wild-percent >dir1/git-wild%percent &&
+ git add dir1/git-wild* &&
+ git commit -m "add some wildcard filenames" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file dir1/git-wild#hash &&
+ test_path_is_file dir1/git-wild\*star &&
+ test_path_is_file dir1/git-wild@at &&
+ test_path_is_file dir1/git-wild%percent
+ ) &&
+ (
+ # delete these carefully, cannot just do "p4 delete"
+ # on files with wildcards; but git-p4 knows how
+ cd "$git" &&
+ git rm dir1/git-wild* &&
+ git commit -m "clean up the wildcards" &&
+ git p4 submit
)
'
test_expect_success 'reinit depot' '
(
cd "$cli" &&
- p4 sync -f &&
rm files &&
p4 delete */* &&
p4 submit -d "delete all files" &&
client_verify $files &&
test_cmp actual "$cli"/filecollide &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files &&
test_cmp actual "$git"/filecollide
'
client_verify $files &&
test_cmp actual "$cli"/filecollide &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files &&
test_cmp actual "$git"/filecollide
'
files="file11 file12 file21 file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
files="file11 file12 file21 file22" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files
'
echo dir1/colA >actual &&
client_verify $files &&
test_cmp actual "$cli"/colA &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files &&
test_cmp actual "$git"/colA
'
test_cmp actual "$cli"/colA &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files &&
test_cmp actual "$cli"/colB &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files &&
test_when_finished cleanup_git &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files
echo dir1/colA >actual &&
client_verify $files &&
test_cmp actual "$cli"/colA &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify $files &&
test_cmp actual "$git"/colA
'
test_cmp actual "$cli"/colA &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files &&
test_cmp actual "$cli"/colB &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files &&
test_when_finished cleanup_git &&
(
cd "$git" &&
- "$GITP4" sync --use-client-spec &&
+ git p4 sync --use-client-spec &&
git merge --ff-only p4/master
) &&
git_verify $files &&
files="cdir1/file11 cdir1/file12" &&
client_verify $files &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
client_verify $files
'
client_view "\"//depot/dir 1/...\" \"//client/cdir 1/...\"" &&
client_verify "cdir 1/file11" "cdir 1/file12" &&
test_when_finished cleanup_git &&
- "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
git_verify "cdir 1/file11" "cdir 1/file12"
'
#
test_expect_success 'edit far away from RCS lines' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
sed -i "s/^line7/line7 edit/" filek &&
git commit -m "filek line7 edit" filek &&
- "$GITP4" submit &&
+ git p4 submit &&
scrub_k_check filek
)
'
#
test_expect_success 'edit near RCS lines' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
sed -i "s/^line4/line4 edit/" filek &&
git commit -m "filek line4 edit" filek &&
- "$GITP4" submit &&
+ git p4 submit &&
scrub_k_check filek
)
'
#
test_expect_success 'edit keyword lines' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
sed -i "/Revision/d" filek &&
git commit -m "filek remove Revision line" filek &&
- "$GITP4" submit &&
+ git p4 submit &&
scrub_k_check filek
)
'
#
test_expect_success 'scrub ko files differently' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
sed -i "s/^line4/line4 edit/" fileko &&
git commit -m "fileko line4 edit" fileko &&
- "$GITP4" submit &&
+ git p4 submit &&
scrub_ko_check fileko &&
! scrub_k_check fileko
)
#
test_expect_success 'do not scrub plain text' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
sed -i "s/^line5/line5 p4 edit/" file_text &&
p4 submit -d "file5 p4 edit"
) &&
- ! "$GITP4" submit &&
+ ! git p4 submit &&
(
# exepct something like:
# file_text - file(s) not opened on this client
# even though the change itself would otherwise apply cleanly.
test_expect_success 'cope with rcs keyword expansion damage' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
git config git-p4.skipSubmitEdit true &&
git add kwfile1.c &&
git commit -m "Zap an RCS kw line" &&
- "$GITP4" submit &&
- "$GITP4" rebase &&
+ git p4 submit &&
+ git p4 rebase &&
git diff p4/master &&
- "$GITP4" commit &&
+ git p4 commit &&
echo "try modifying in both" &&
cd "$cli" &&
p4 edit kwfile1.c &&
echo "line from git at the top" | cat - kwfile1.c >kwfile1.c.new &&
mv kwfile1.c.new kwfile1.c &&
git commit -m "Add line in git at the top" kwfile1.c &&
- "$GITP4" rebase &&
- "$GITP4" submit
+ git p4 rebase &&
+ git p4 submit
)
'
cat kwdelfile.c &&
grep 1 kwdelfile.c
) &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
grep Revision kwdelfile.c &&
git commit -m "Delete a file containing RCS keywords" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
# work fine without any special handling.
test_expect_success 'Add keywords in git which match the default p4 values' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
echo "NewKW: \$Revision\$" >>kwfile1.c &&
git commit -m "Adding RCS keywords in git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
#
test_expect_failure 'Add keywords in git which do not match the default p4 values' '
test_when_finished cleanup_git &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
echo "NewKW2: \$Revision:1\$" >>kwfile1.c &&
git commit -m "Adding RCS keywords in git" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
- "$GITP4" submit
+ git p4 submit
) &&
(
cd "$cli" &&
p4 add -t ktext merge2.c &&
p4 submit -d "add merge test file"
) &&
- "$GITP4" clone --dest="$git" //depot &&
+ git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
sed -e "/Hello/d" merge2.c >merge2.c.tmp &&
test -f merge2.c &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.attemptRCSCleanup true &&
- !(echo "s" | "$GITP4" submit) &&
+ !(echo "s" | git p4 submit) &&
git rebase --skip &&
! test -f merge2.c
)
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 label tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+# Basic p4 label import tests.
+#
+test_expect_success 'basic p4 labels' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ echo f1 >main/f1 &&
+ p4 add main/f1 &&
+ p4 submit -d "main/f1" &&
+
+ echo f2 >main/f2 &&
+ p4 add main/f2 &&
+ p4 submit -d "main/f2" &&
+
+ echo f3 >main/file_with_\$metachar &&
+ p4 add main/file_with_\$metachar &&
+ p4 submit -d "file with metachar" &&
+
+ p4 tag -l TAG_F1_ONLY main/f1 &&
+ p4 tag -l TAG_WITH\$_SHELL_CHAR main/... &&
+ p4 tag -l this_tag_will_be\ skipped main/... &&
+
+ echo f4 >main/f4 &&
+ p4 add main/f4 &&
+ p4 submit -d "main/f4" &&
+
+ p4 label -i <<-EOF &&
+ Label: TAG_LONG_LABEL
+ Description:
+ A Label first line
+ A Label second line
+ View: //depot/...
+ EOF
+
+ p4 tag -l TAG_LONG_LABEL ... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ cd "$git" &&
+ git config git-p4.labelImportRegexp ".*TAG.*" &&
+ git p4 sync --import-labels --verbose &&
+
+ git tag &&
+ git tag >taglist &&
+ test_line_count = 3 taglist &&
+
+ cd main &&
+ git checkout TAG_F1_ONLY &&
+ ! test -f f2 &&
+ git checkout TAG_WITH\$_SHELL_CHAR &&
+ test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+
+ git show TAG_LONG_LABEL | grep -q "A Label second line"
+ )
+'
+# Test some label corner cases:
+#
+# - two tags on the same file; both should be available
+# - a tag that is only on one file; this kind of tag
+# cannot be imported (at least not easily).
+
+test_expect_success 'two labels on the same changelist' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ p4 edit main/f1 main/f2 &&
+ echo "hello world" >main/f1 &&
+ echo "not in the tag" >main/f2 &&
+ p4 submit -d "main/f[12]: testing two labels" &&
+
+ p4 tag -l TAG_F1_1 main/... &&
+ p4 tag -l TAG_F1_2 main/... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ cd "$git" &&
+ git p4 sync --import-labels &&
+
+ git tag | grep TAG_F1 &&
+ git tag | grep -q TAG_F1_1 &&
+ git tag | grep -q TAG_F1_2 &&
+
+ cd main &&
+
+ git checkout TAG_F1_1 &&
+ ls &&
+ test -f f1 &&
+
+ git checkout TAG_F1_2 &&
+ ls &&
+ test -f f1
+ )
+'
+
+# Export some git tags to p4
+test_expect_success 'export git tags to p4' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git tag -m "A tag created in git:xyzzy" GIT_TAG_1 &&
+ echo "hello world" >main/f10 &&
+ git add main/f10 &&
+ git commit -m "Adding file for export test" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit &&
+ git tag -m "Another git tag" GIT_TAG_2 &&
+ git tag LIGHTWEIGHT_TAG &&
+ git p4 rebase --import-labels --verbose &&
+ git p4 submit --export-labels --verbose
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ p4 labels ... | grep GIT_TAG_1 &&
+ p4 labels ... | grep GIT_TAG_2 &&
+ p4 labels ... | grep LIGHTWEIGHT_TAG &&
+ p4 label -o GIT_TAG_1 | grep "tag created in git:xyzzy" &&
+ p4 sync ...@GIT_TAG_1 &&
+ ! test -f main/f10
+ p4 sync ...@GIT_TAG_2 &&
+ test -f main/f10
+ )
+'
+
+# Export a tag from git where an affected file is deleted later on
+# Need to create git tags after rebase, since only then can the
+# git commits be mapped to p4 changelists.
+test_expect_success 'export git tags to p4 with deletion' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git p4 sync --import-labels &&
+ echo "deleted file" >main/deleted_file &&
+ git add main/deleted_file &&
+ git commit -m "create deleted file" &&
+ git rm main/deleted_file &&
+ echo "new file" >main/f11 &&
+ git add main/f11 &&
+ git commit -m "delete the deleted file" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit &&
+ git p4 rebase --import-labels --verbose &&
+ git tag -m "tag on deleted file" GIT_TAG_ON_DELETED HEAD~1 &&
+ git tag -m "tag after deletion" GIT_TAG_AFTER_DELETION HEAD &&
+ git p4 submit --export-labels --verbose
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ p4 sync ...@GIT_TAG_ON_DELETED &&
+ test -f main/deleted_file &&
+ p4 sync ...@GIT_TAG_AFTER_DELETION &&
+ ! test -f main/deleted_file &&
+ echo "checking label contents" &&
+ p4 label -o GIT_TAG_ON_DELETED | grep "tag on deleted file"
+ )
+'
+
+# Create a tag in git that cannot be exported to p4
+test_expect_success 'tag that cannot be exported' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git checkout -b a_branch &&
+ echo "hello" >main/f12 &&
+ git add main/f12 &&
+ git commit -m "adding f12" &&
+ git tag -m "tag on a_branch" GIT_TAG_ON_A_BRANCH &&
+ git checkout master &&
+ git p4 submit --export-labels
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ !(p4 labels | grep GIT_TAG_ON_A_BRANCH)
+ )
+'
+
+test_expect_success 'use git config to enable import/export of tags' '
+ git p4 clone --verbose --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git config git-p4.exportLabels true &&
+ git config git-p4.importLabels true &&
+ git tag CFG_A_GIT_TAG &&
+ git p4 rebase --verbose &&
+ git p4 submit --verbose &&
+ git tag &&
+ git tag | grep TAG_F1_1
+ ) &&
+ (
+ cd "$cli" &&
+ p4 labels &&
+ p4 labels | grep CFG_A_GIT_TAG
+ )
+'
+
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then
+ # we are in full-on bash mode
+ true
+elif type bash >/dev/null 2>&1; then
+ # execute in full-on bash mode
+ unset POSIXLY_CORRECT
+ exec bash "$0" "$@"
+else
+ echo '1..0 #SKIP skipping bash completion tests; bash not available'
+ exit 0
+fi
+
+test_description='test bash completion'
+
+. ./test-lib.sh
+
+complete ()
+{
+ # do nothing
+ return 0
+}
+
+. "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
+
+# We don't need this function to actually join words or do anything special.
+# Also, it's cleaner to avoid touching bash's internal completion variables.
+# So let's override it with a minimal version for testing purposes.
+_get_comp_words_by_ref ()
+{
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=${_words[_cword]}
+ ;;
+ prev)
+ prev=${_words[_cword-1]}
+ ;;
+ words)
+ words=("${_words[@]}")
+ ;;
+ cword)
+ cword=$_cword
+ ;;
+ esac
+ shift
+ done
+}
+
+print_comp ()
+{
+ local IFS=$'\n'
+ echo "${COMPREPLY[*]}" > out
+}
+
+run_completion ()
+{
+ local -a COMPREPLY _words
+ local _cword
+ _words=( $1 )
+ (( _cword = ${#_words[@]} - 1 ))
+ __git_wrap_git && print_comp
+}
+
+test_completion ()
+{
+ test $# -gt 1 && echo "$2" > expected
+ run_completion "$@" &&
+ test_cmp expected out
+}
+
+newline=$'\n'
+
+test_expect_success '__gitcomp - trailing space - options' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --reuse-message=Z
+ --reedit-message=Z
+ --reset-author Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="--re" &&
+ __gitcomp "--dry-run --reuse-message= --reedit-message=
+ --reset-author" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - trailing space - config keys' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.Z
+ branch.autosetupmerge Z
+ branch.autosetuprebase Z
+ browser.Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="br" &&
+ __gitcomp "branch. branch.autosetupmerge
+ branch.autosetuprebase browser." &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - option parameter' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ recursive Z
+ resolve Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="--strategy=re" &&
+ __gitcomp "octopus ours recursive resolve subtree
+ " "" "re" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - prefix' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.maint.merge Z
+ branch.maint.mergeoptions Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="branch.me" &&
+ __gitcomp "remote merge mergeoptions rebase
+ " "branch.maint." "me" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - suffix' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.master.Z
+ branch.maint.Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="branch.me" &&
+ __gitcomp "master maint next pu
+ " "branch." "ma" "." &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success 'basic' '
+ run_completion "git \"\"" &&
+ # built-in
+ grep -q "^add \$" out &&
+ # script
+ grep -q "^filter-branch \$" out &&
+ # plumbing
+ ! grep -q "^ls-files \$" out &&
+
+ run_completion "git f" &&
+ ! grep -q -v "^f" out
+'
+
+test_expect_success 'double dash "git" itself' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --paginate Z
+ --no-pager Z
+ --git-dir=
+ --bare Z
+ --version Z
+ --exec-path Z
+ --exec-path=
+ --html-path Z
+ --info-path Z
+ --work-tree=
+ --namespace=
+ --no-replace-objects Z
+ --help Z
+ EOF
+ test_completion "git --"
+'
+
+test_expect_success 'double dash "git checkout"' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --quiet Z
+ --ours Z
+ --theirs Z
+ --track Z
+ --no-track Z
+ --merge Z
+ --conflict=
+ --orphan Z
+ --patch Z
+ EOF
+ test_completion "git checkout --"
+'
+
+test_expect_success 'general options' '
+ test_completion "git --ver" "--version " &&
+ test_completion "git --hel" "--help " &&
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --exec-path Z
+ --exec-path=
+ EOF
+ test_completion "git --exe" &&
+ test_completion "git --htm" "--html-path " &&
+ test_completion "git --pag" "--paginate " &&
+ test_completion "git --no-p" "--no-pager " &&
+ test_completion "git --git" "--git-dir=" &&
+ test_completion "git --wor" "--work-tree=" &&
+ test_completion "git --nam" "--namespace=" &&
+ test_completion "git --bar" "--bare " &&
+ test_completion "git --inf" "--info-path " &&
+ test_completion "git --no-r" "--no-replace-objects "
+'
+
+test_expect_success 'general options plus command' '
+ test_completion "git --version check" "checkout " &&
+ test_completion "git --paginate check" "checkout " &&
+ test_completion "git --git-dir=foo check" "checkout " &&
+ test_completion "git --bare check" "checkout " &&
+ test_completion "git --help des" "describe " &&
+ test_completion "git --exec-path=foo check" "checkout " &&
+ test_completion "git --html-path check" "checkout " &&
+ test_completion "git --no-pager check" "checkout " &&
+ test_completion "git --work-tree=foo check" "checkout " &&
+ test_completion "git --namespace=foo check" "checkout " &&
+ test_completion "git --paginate check" "checkout " &&
+ test_completion "git --info-path check" "checkout " &&
+ test_completion "git --no-replace-objects check" "checkout "
+'
+
+test_done
static void show_dates(char **argv, struct timeval *now)
{
- char buf[128];
+ struct strbuf buf = STRBUF_INIT;
for (; *argv; argv++) {
time_t t = atoi(*argv);
- show_date_relative(t, 0, now, buf, sizeof(buf));
- printf("%s -> %s\n", *argv, buf);
+ show_date_relative(t, 0, now, &buf);
+ printf("%s -> %s\n", *argv, buf.buf);
}
+ strbuf_release(&buf);
}
static void parse_dates(char **argv, struct timeval *now)
--- /dev/null
+#include "cache.h"
+#include "mergesort.h"
+
+struct line {
+ char *text;
+ struct line *next;
+};
+
+static void *get_next(const void *a)
+{
+ return ((const struct line *)a)->next;
+}
+
+static void set_next(void *a, void *b)
+{
+ ((struct line *)a)->next = b;
+}
+
+static int compare_strings(const void *a, const void *b)
+{
+ const struct line *x = a, *y = b;
+ return strcmp(x->text, y->text);
+}
+
+int main(int argc, const char **argv)
+{
+ struct line *line, *p = NULL, *lines = NULL;
+ struct strbuf sb = STRBUF_INIT;
+
+ for (;;) {
+ if (strbuf_getwholeline(&sb, stdin, '\n'))
+ break;
+ line = xmalloc(sizeof(struct line));
+ line->text = strbuf_detach(&sb, NULL);
+ if (p) {
+ line->next = p->next;
+ p->next = line;
+ } else {
+ line->next = NULL;
+ lines = line;
+ }
+ p = line;
+ }
+
+ lines = llist_mergesort(lines, get_next, set_next, compare_strings);
+
+ while (lines) {
+ printf("%s", lines->text);
+ lines = lines->next;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * test-revision-walking.c: test revision walking API.
+ *
+ * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+
+static void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, " %m %s", &sb, &ctx);
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+static int run_revision_walk(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, "--all", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+ int got_revision = 0;
+
+ init_revisions(&rev, NULL);
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ while ((commit = get_revision(&rev)) != NULL) {
+ print_commit(commit);
+ got_revision = 1;
+ }
+
+ reset_revision_walk();
+ return got_revision;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ return 1;
+
+ if (!strcmp(argv[1], "run-twice")) {
+ printf("1st\n");
+ if (!run_revision_walk())
+ return 1;
+ printf("2nd\n");
+ if (!run_revision_walk())
+ return 1;
+
+ return 0;
+ }
+
+ fprintf(stderr, "check usage\n");
+ return 1;
+}
data->import_marks = strbuf_detach(&arg, NULL);
} else if (mandatory) {
die("Unknown mandatory capability %s. This remote "
- "helper probably needs newer version of Git.\n",
+ "helper probably needs newer version of Git.",
capname);
}
}
status = REF_STATUS_REMOTE_REJECT;
refname = buf->buf + 6;
} else
- die("expected ok/error, helper said '%s'\n", buf->buf);
+ die("expected ok/error, helper said '%s'", buf->buf);
msg = strchr(refname, ' ');
if (msg) {
#include "branch.h"
#include "url.h"
#include "submodule.h"
+#include "string-list.h"
/* rsync support */
transport->progress = verbosity >= 0 && isatty(2);
}
+static void die_with_unpushed_submodules(struct string_list *needs_pushing)
+{
+ int i;
+
+ fprintf(stderr, "The following submodule paths contain changes that can\n"
+ "not be found on any remote:\n");
+ for (i = 0; i < needs_pushing->nr; i++)
+ printf(" %s\n", needs_pushing->items[i].string);
+ fprintf(stderr, "\nPlease try\n\n"
+ " git push --recurse-submodules=on-demand\n\n"
+ "or cd to the path and use\n\n"
+ " git push\n\n"
+ "to push them to a remote.\n\n");
+
+ string_list_clear(needs_pushing, 0);
+
+ die("Aborting.");
+}
+
int transport_push(struct transport *transport,
int refspec_nr, const char **refspec, int flags,
int *nonfastforward)
flags & TRANSPORT_PUSH_MIRROR,
flags & TRANSPORT_PUSH_FORCE);
- if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
+ if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
struct ref *ref = remote_refs;
for (; ref; ref = ref->next)
if (!is_null_sha1(ref->new_sha1) &&
- check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
- die("There are unpushed submodules, aborting.");
+ !push_unpushed_submodules(ref->new_sha1,
+ transport->remote->name))
+ die ("Failed to push all needed submodules!");
+ }
+
+ if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+ TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {
+ struct ref *ref = remote_refs;
+ struct string_list needs_pushing;
+
+ memset(&needs_pushing, 0, sizeof(struct string_list));
+ needs_pushing.strdup_strings = 1;
+ for (; ref; ref = ref->next)
+ if (!is_null_sha1(ref->new_sha1) &&
+ find_unpushed_submodules(ref->new_sha1,
+ transport->remote->name, &needs_pushing))
+ die_with_unpushed_submodules(&needs_pushing);
}
push_ret = transport->push_refs(transport, remote_refs, flags);
#define TRANSPORT_PUSH_SET_UPSTREAM 32
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
#define TRANSPORT_PUSH_PRUNE 128
+#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
o->result.initialized = 1;
o->result.timestamp.sec = o->src_index->timestamp.sec;
o->result.timestamp.nsec = o->src_index->timestamp.nsec;
+ o->result.version = o->src_index->version;
o->merge_size = len;
mark_all_ce_unused(o->src_index);
struct cache_entry *a = src[1];
if (o->merge_size != 1)
- return error("Cannot do a bind merge of %d trees\n",
+ return error("Cannot do a bind merge of %d trees",
o->merge_size);
if (a && old)
return o->gently ? -1 :
--- /dev/null
+#include "varint.h"
+
+uintmax_t decode_varint(const unsigned char **bufp)
+{
+ const unsigned char *buf = *bufp;
+ unsigned char c = *buf++;
+ uintmax_t val = c & 127;
+ while (c & 128) {
+ val += 1;
+ if (!val || MSB(val, 7))
+ return 0; /* overflow */
+ c = *buf++;
+ val = (val << 7) + (c & 127);
+ }
+ *bufp = buf;
+ return val;
+}
+
+int encode_varint(uintmax_t value, unsigned char *buf)
+{
+ unsigned char varint[16];
+ unsigned pos = sizeof(varint) - 1;
+ varint[pos] = value & 127;
+ while (value >>= 7)
+ varint[--pos] = 128 | (--value & 127);
+ if (buf)
+ memcpy(buf, varint + pos, sizeof(varint) - pos);
+ return sizeof(varint) - pos;
+}
--- /dev/null
+#ifndef VARINT_H
+#define VARINT_H
+
+#include "git-compat-util.h"
+
+extern int encode_varint(uintmax_t, unsigned char *);
+extern uintmax_t decode_varint(const unsigned char **);
+
+#endif /* VARINT_H */
int ch;
if (!type || t[1] != ' ')
- die("invalid property line: %s\n", t);
+ die("invalid property line: %s", t);
len = atoi(&t[2]);
strbuf_reset(&val);
buffer_read_binary(&input, &val, len);
strbuf_reset(&key);
continue;
default:
- die("invalid property line: %s\n", t);
+ die("invalid property line: %s", t);
}
}
}
static void (*try_to_free_routine)(size_t size) = do_nothing;
+static void memory_limit_check(size_t size)
+{
+ static int limit = -1;
+ if (limit == -1) {
+ const char *env = getenv("GIT_ALLOC_LIMIT");
+ limit = env ? atoi(env) * 1024 : 0;
+ }
+ if (limit && size > limit)
+ die("attempting to allocate %"PRIuMAX" over limit %d",
+ (intmax_t)size, limit);
+}
+
try_to_free_t set_try_to_free_routine(try_to_free_t routine)
{
try_to_free_t old = try_to_free_routine;
void *xmalloc(size_t size)
{
- void *ret = malloc(size);
+ void *ret;
+
+ memory_limit_check(size);
+ ret = malloc(size);
if (!ret && !size)
ret = malloc(1);
if (!ret) {
void *xrealloc(void *ptr, size_t size)
{
- void *ret = realloc(ptr, size);
+ void *ret;
+
+ memory_limit_check(size);
+ ret = realloc(ptr, size);
if (!ret && !size)
ret = realloc(ptr, 1);
if (!ret) {
void *xcalloc(size_t nmemb, size_t size)
{
- void *ret = calloc(nmemb, size);
+ void *ret;
+
+ memory_limit_check(size * nmemb);
+ ret = calloc(nmemb, size);
if (!ret && (!nmemb || !size))
ret = calloc(1, 1);
if (!ret) {
#include "remote.h"
#include "refs.h"
#include "submodule.h"
+#include "column.h"
static char default_wt_status_colors[][COLOR_MAXLEN] = {
GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
{
int i;
struct strbuf buf = STRBUF_INIT;
+ static struct string_list output = STRING_LIST_INIT_DUP;
+ struct column_options copts;
if (!l->nr)
return;
for (i = 0; i < l->nr; i++) {
struct string_list_item *it;
+ const char *path;
it = &(l->items[i]);
+ path = quote_path(it->string, strlen(it->string),
+ &buf, s->prefix);
+ if (column_active(s->colopts)) {
+ string_list_append(&output, path);
+ continue;
+ }
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
- "%s\n", quote_path(it->string, strlen(it->string),
- &buf, s->prefix));
+ "%s\n", path);
}
+
+ strbuf_release(&buf);
+ if (!column_active(s->colopts))
+ return;
+
+ strbuf_addf(&buf, "%s#\t%s",
+ color(WT_STATUS_HEADER, s),
+ color(WT_STATUS_UNTRACKED, s));
+ memset(&copts, 0, sizeof(copts));
+ copts.padding = 1;
+ copts.indent = buf.buf;
+ if (want_color(s->use_color))
+ copts.nl = GIT_COLOR_RESET "\n";
+ print_columns(&output, s->colopts, &copts);
+ string_list_clear(&output, 0);
strbuf_release(&buf);
}
enum untracked_status_type show_untracked_files;
const char *ignore_submodule_arg;
char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
+ unsigned colopts;
int null_termination;
int show_branch;
return ret;
}
-struct xdiff_emit_hunk_state {
- xdiff_emit_hunk_consume_fn consume;
- void *consume_callback_data;
-};
-
-static int process_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
- xdemitconf_t const *xecfg)
-{
- long s1, s2, same, p_next, t_next;
- xdchange_t *xch, *xche;
- struct xdiff_emit_hunk_state *state = ecb->priv;
- xdiff_emit_hunk_consume_fn fn = state->consume;
- void *consume_callback_data = state->consume_callback_data;
-
- for (xch = xscr; xch; xch = xche->next) {
- xche = xdl_get_hunk(xch, xecfg);
-
- s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
- s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
- same = s2 + XDL_MAX(xch->i1 - s1, 0);
- p_next = xche->i1 + xche->chg1;
- t_next = xche->i2 + xche->chg2;
-
- fn(consume_callback_data, same, p_next, t_next);
- }
- return 0;
-}
-
-int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
- xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
- xpparam_t const *xpp, xdemitconf_t *xecfg)
-{
- struct xdiff_emit_hunk_state state;
- xdemitcb_t ecb;
-
- memset(&state, 0, sizeof(state));
- memset(&ecb, 0, sizeof(ecb));
- state.consume = fn;
- state.consume_callback_data = consume_callback_data;
- xecfg->emit_func = (void (*)())process_diff;
- ecb.priv = &state;
- return xdi_diff(mf1, mf2, xpp, xecfg, &ecb);
-}
-
int read_mmfile(mmfile_t *ptr, const char *filename)
{
struct stat st;
#include "xdiff/xdiff.h"
typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
-typedef void (*xdiff_emit_hunk_consume_fn)(void *, long, long, long);
int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
xdiff_emit_consume_fn fn, void *consume_callback_data,
xpparam_t const *xpp, xdemitconf_t const *xecfg);
-int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
- xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
- xpparam_t const *xpp, xdemitconf_t *xecfg);
int parse_hunk_header(char *line, int len,
int *ob, int *on,
int *nb, int *nn);
typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a,
+ long start_b, long count_b,
+ void *cb_data);
+
typedef struct s_xdemitconf {
long ctxlen;
long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
- void (*emit_func)();
+ xdl_emit_hunk_consume_func_t hunk_func;
} xdemitconf_t;
typedef struct s_bdiffparam {
}
}
+static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch, *xche;
+
+ for (xch = xscr; xch; xch = xche->next) {
+ xche = xdl_get_hunk(xch, xecfg);
+ if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
+ xch->i2, xche->i2 + xche->chg2 - xch->i2,
+ ecb->priv) < 0)
+ return -1;
+ }
+ return 0;
+}
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
xdchange_t *xscr;
xdfenv_t xe;
- emit_func_t ef = xecfg->emit_func ?
- (emit_func_t)xecfg->emit_func : xdl_emit_diff;
+ emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff;
if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
*
*/
+#include <limits.h>
+#include <assert.h>
#include "xinclude.h"
return data;
}
-
-void *xdl_cha_first(chastore_t *cha) {
- chanode_t *sncur;
-
- if (!(cha->sncur = sncur = cha->head))
- return NULL;
-
- cha->scurr = 0;
-
- return (char *) sncur + sizeof(chanode_t) + cha->scurr;
-}
-
-
-void *xdl_cha_next(chastore_t *cha) {
- chanode_t *sncur;
-
- if (!(sncur = cha->sncur))
- return NULL;
- cha->scurr += cha->isize;
- if (cha->scurr == sncur->icurr) {
- if (!(sncur = cha->sncur = sncur->next))
- return NULL;
- cha->scurr = 0;
- }
-
- return (char *) sncur + sizeof(chanode_t) + cha->scurr;
-}
-
-
long xdl_guess_lines(mmfile_t *mf, long sample) {
long nl = 0, size, tsize = 0;
char const *data, *cur, *top;
return ha;
}
+#ifdef XDL_FAST_HASH
+
+#define ONEBYTES 0x0101010101010101ul
+#define NEWLINEBYTES 0x0a0a0a0a0a0a0a0aul
+#define HIGHBITS 0x8080808080808080ul
+
+/* Return the high bit set in the first byte that is a zero */
+static inline unsigned long has_zero(unsigned long a)
+{
+ return ((a - ONEBYTES) & ~a) & HIGHBITS;
+}
+
+static inline long count_masked_bytes(unsigned long mask)
+{
+ if (sizeof(long) == 8) {
+ /*
+ * Jan Achrenius on G+: microoptimized version of
+ * the simpler "(mask & ONEBYTES) * ONEBYTES >> 56"
+ * that works for the bytemasks without having to
+ * mask them first.
+ */
+ return mask * 0x0001020304050608 >> 56;
+ } else {
+ /*
+ * Modified Carl Chatfield G+ version for 32-bit *
+ *
+ * (a) gives us
+ * -1 (0, ff), 0 (ffff) or 1 (ffffff)
+ * (b) gives us
+ * 0 for 0, 1 for (ff ffff ffffff)
+ * (a+b+1) gives us
+ * correct 0-3 bytemask count result
+ */
+ long a = (mask - 256) >> 23;
+ long b = mask & 1;
+ return a + b + 1;
+ }
+}
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+ unsigned long hash = 5381;
+ unsigned long a = 0, mask = 0;
+ char const *ptr = *data;
+ char const *end = top - sizeof(unsigned long) + 1;
+
+ if (flags & XDF_WHITESPACE_FLAGS)
+ return xdl_hash_record_with_whitespace(data, top, flags);
+
+ ptr -= sizeof(unsigned long);
+ do {
+ hash += hash << 5;
+ hash ^= a;
+ ptr += sizeof(unsigned long);
+ if (ptr >= end)
+ break;
+ a = *(unsigned long *)ptr;
+ /* Do we have any '\n' bytes in this word? */
+ mask = has_zero(a ^ NEWLINEBYTES);
+ } while (!mask);
+
+ if (ptr >= end) {
+ /*
+ * There is only a partial word left at the end of the
+ * buffer. Because we may work with a memory mapping,
+ * we have to grab the rest byte by byte instead of
+ * blindly reading it.
+ *
+ * To avoid problems with masking in a signed value,
+ * we use an unsigned char here.
+ */
+ const char *p;
+ for (p = top - 1; p >= ptr; p--)
+ a = (a << 8) + *((const unsigned char *)p);
+ mask = has_zero(a ^ NEWLINEBYTES);
+ if (!mask)
+ /*
+ * No '\n' found in the partial word. Make a
+ * mask that matches what we read.
+ */
+ mask = 1UL << (8 * (top - ptr) + 7);
+ }
+
+ /* The mask *below* the first high bit set */
+ mask = (mask - 1) & ~mask;
+ mask >>= 7;
+ hash += hash << 5;
+ hash ^= a & mask;
+
+ /* Advance past the last (possibly partial) word */
+ ptr += count_masked_bytes(mask);
+
+ if (ptr < top) {
+ assert(*ptr == '\n');
+ ptr++;
+ }
+
+ *data = ptr;
+
+ return hash;
+}
+
+#else /* XDL_FAST_HASH */
unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
unsigned long ha = 5381;
return ha;
}
+#endif /* XDL_FAST_HASH */
unsigned int xdl_hashbits(unsigned int size) {
unsigned int val = 1, bits = 0;
return str - out;
}
-
-long xdl_atol(char const *str, char const **next) {
- long val, base;
- char const *top;
-
- for (top = str; XDL_ISDIGIT(*top); top++);
- if (next)
- *next = top;
- for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
- val += base * (long)(*top - '0');
- return val;
-}
-
-
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
const char *func, long funclen, xdemitcb_t *ecb) {
int nb = 0;
int xdl_cha_init(chastore_t *cha, long isize, long icount);
void xdl_cha_free(chastore_t *cha);
void *xdl_cha_alloc(chastore_t *cha);
-void *xdl_cha_first(chastore_t *cha);
-void *xdl_cha_next(chastore_t *cha);
long xdl_guess_lines(mmfile_t *mf, long sample);
int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
unsigned long xdl_hash_record(char const **data, char const *top, long flags);
unsigned int xdl_hashbits(unsigned int size);
int xdl_num_out(char *out, long val);
-long xdl_atol(char const *str, char const **next);
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
const char *func, long funclen, xdemitcb_t *ecb);
int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,