/config.mak.autogen
/config.mak.append
/configure
+/unicode
/tags
/TAGS
/cscope*
judgement call, the decision based more on real world
constraints people face than what the paper standard says.
+ - Fixing style violations while working on a real change as a
+ preparatory clean-up step is good, but otherwise avoid useless code
+ churn for the sake of conforming to the style.
+
+ "Once it _is_ in the tree, it's not really worth the patch noise to
+ go and fix it up."
+ Cf. http://article.gmane.org/gmane.linux.kernel/943020
+
Make your code readable and sensible, and don't try to be clever.
As for more concrete guidelines, just imitate the existing code
- We use tabs for indentation.
- - Case arms are indented at the same depth as case and esac lines.
+ - Case arms are indented at the same depth as case and esac lines,
+ like this:
+
+ case "$variable" in
+ pattern1)
+ do this
+ ;;
+ pattern2)
+ do that
+ ;;
+ esac
- Redirection operators should be written with space before, but no
space after them. In other words, write 'echo test >"$file"'
redirection target in a variable (as shown above), our code does so
because some versions of bash issue a warning without the quotes.
+ (incorrect)
+ cat hello > world < universe
+ echo hello >$world
+
+ (correct)
+ cat hello >world <universe
+ echo hello >"$world"
+
- We prefer $( ... ) for command substitution; unlike ``, it
properly nests. It should have been the way Bourne spelled
it from day one, but unfortunately isn't.
"then" should be on the next line for if statements, and "do"
should be on the next line for "while" and "for".
+ (incorrect)
+ if test -f hello; then
+ do this
+ fi
+
+ (correct)
+ if test -f hello
+ then
+ do this
+ fi
+
- We prefer "test" over "[ ... ]".
- We do not write the noiseword "function" in front of shell
functions.
- - We prefer a space between the function name and the parentheses. The
- opening "{" should also be on the same line.
- E.g.: my_function () {
+ - We prefer a space between the function name and the parentheses,
+ and no space inside the parentheses. The opening "{" should also
+ be on the same line.
+
+ (incorrect)
+ my_function(){
+ ...
+
+ (correct)
+ my_function () {
+ ...
- As to use of grep, stick to a subset of BRE (namely, no \{m,n\},
[::], [==], or [..]) for portability.
interface translatable. See "Marking strings for translation" in
po/README.
+ - We do not write our "test" command with "-a" and "-o" and use "&&"
+ or "||" to concatenate multiple "test" commands instead, because
+ the use of "-a/-o" is often error-prone. E.g.
+
+ test -n "$x" -a "$a" = "$b"
+
+ is buggy and breaks when $x is "=", but
+
+ test -n "$x" && test "$a" = "$b"
+
+ does not have such a problem.
+
+
For C programs:
- We use tabs to indent, and interpret tabs as taking up to
of "else if" statements, it can make sense to add braces to
single line blocks.
- - We try to avoid assignments inside if().
+ - We try to avoid assignments in the condition of an "if" statement.
- Try to make your code understandable. You may put comments
in, but comments invariably tend to stale out when the code
- Double negation is often harder to understand than no negation
at all.
+ - There are two schools of thought when it comes to comparison,
+ especially inside a loop. Some people prefer to have the less stable
+ value on the left hand side and the more stable value on the right hand
+ side, e.g. if you have a loop that counts variable i down to the
+ lower bound,
+
+ while (i > lower_bound) {
+ do something;
+ i--;
+ }
+
+ Other people prefer to have the textual order of values match the
+ actual order of values in their comparison, so that they can
+ mentally draw a number line from left to right and place these
+ values in order, i.e.
+
+ while (lower_bound < i) {
+ do something;
+ i--;
+ }
+
+ Both are valid, and we use both. However, the more "stable" the
+ stable side becomes, the more we tend to prefer the former
+ (comparison with a constant, "i > 0", is an extreme example).
+ Just do not mix styles in the same part of the code and mimic
+ existing styles in the neighbourhood.
+
+ - There are two schools of thought when it comes to splitting a long
+ logical line into multiple lines. Some people push the second and
+ subsequent lines far enough to the right with tabs and align them:
+
+ if (the_beginning_of_a_very_long_expression_that_has_to ||
+ span_more_than_a_single_line_of ||
+ the_source_text) {
+ ...
+
+ while other people prefer to align the second and the subsequent
+ lines with the column immediately inside the opening parenthesis,
+ with tabs and spaces, following our "tabstop is always a multiple
+ of 8" convention:
+
+ if (the_beginning_of_a_very_long_expression_that_has_to ||
+ span_more_than_a_single_line_of ||
+ the_source_text) {
+ ...
+
+ Both are valid, and we use both. Again, just do not mix styles in
+ the same part of the code and mimic existing styles in the
+ neighbourhood.
+
+ - When splitting a long logical line, some people change line before
+ a binary operator, so that the result looks like a parse tree when
+ you turn your head 90-degrees counterclockwise:
+
+ if (the_beginning_of_a_very_long_expression_that_has_to
+ || span_more_than_a_single_line_of_the_source_text) {
+
+ while other people prefer to leave the operator at the end of the
+ line:
+
+ if (the_beginning_of_a_very_long_expression_that_has_to ||
+ span_more_than_a_single_line_of_the_source_text) {
+
+ Both are valid, but we tend to use the latter more, unless the
+ expression gets fairly complex, in which case the former tends to
+ be easier to read. Again, just do not mix styles in the same part
+ of the code and mimic existing styles in the neighbourhood.
+
+ - When splitting a long logical line, with everything else being
+ equal, it is preferable to split after the operator at higher
+ level in the parse tree. That is, this is more preferable:
+
+ if (a_very_long_variable * that_is_used_in +
+ a_very_long_expression) {
+ ...
+
+ than
+
+ if (a_very_long_variable *
+ that_is_used_in + a_very_long_expression) {
+ ...
+
- Some clever tricks, like using the !! operator with arithmetic
constructs, can be extremely confusing to others. Avoid them,
unless there is a compelling reason to use them.
SP_ARTICLES += howto/recover-corrupted-object-harder
SP_ARTICLES += howto/rebuild-from-update-hook
SP_ARTICLES += howto/rebase-from-internal-branch
+SP_ARTICLES += howto/keep-canonical-history-correct
SP_ARTICLES += howto/maintain-git
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
SP_ARTICLES += $(API_DOCS)
--- /dev/null
+Git v1.9.4 Release Notes
+========================
+
+Fixes since v1.9.3
+------------------
+
+ * Commands that take pathspecs on the command line misbehaved when
+ the pathspec is given as an absolute pathname (which is a
+ practice not particularly encouraged) that points at a symbolic
+ link in the working tree.
+
+ * An earlier fix to the shell prompt script (in contrib/) for using
+ the PROMPT_COMMAND interface did not correctly check if the extra
+ code path needs to trigger, causing the branch name not to appear
+ when 'promptvars' option is disabled in bash or PROMPT_SUBST is
+ unset in zsh.
The default prefix for "git svn" has changed in Git 2.0. For a long
time, "git svn" created its remote-tracking branches directly under
refs/remotes, but it now places them under refs/remotes/origin/ unless
-it is told otherwise with its --prefix option.
+it is told otherwise with its "--prefix" option.
Updates since v1.9 series
UI, Workflows & Features
* The "multi-mail" post-receive hook (in contrib/) has been updated
- to a more recent version from the upstream.
+ to a more recent version from upstream.
+
+ * The "remote-hg/bzr" remote-helper interfaces (used to be in
+ contrib/) are no more. They are now maintained separately as
+ third-party plug-ins in their own repositories.
* "git gc --aggressive" learned "--depth" option and
"gc.aggressiveDepth" configuration variable to allow use of a less
single strand-of-pearls is broken in its output.
* The "rev-parse --parseopt" mechanism used by scripted Porcelains to
- parse command line options and to give help text learned to take
+ parse command-line options and to give help text learned to take
the argv-help (the placeholder string for an option parameter,
e.g. "key-id" in "--gpg-sign=<key-id>").
* The pattern to find where the function begins in C/C++ used in
- "diff" and "grep -p" has been updated to help C++ source better.
+ "diff" and "grep -p" has been updated to improve viewing C++
+ sources.
* "git rebase" learned to interpret a lone "-" as "@{-1}", the
branch that we were previously on.
"--sort=version:refname".
* Discard the accumulated "heuristics" to guess from which branch the
- result wants to be pulled from and make sure what the end user
+ result wants to be pulled from and make sure that what the end user
specified is not second-guessed by "git request-pull", to avoid
mistakes. When you pushed out your 'master' branch to your public
repository as 'for-linus', use the new "master:for-linus" syntax to
* "git grep" learned to behave in a way similar to native grep when
"-h" (no header) and "-c" (count) options are given.
- * "git push" via transport-helper interface (e.g. remote-hg) has
- been updated to allow forced ref updates in a way similar to the
- natively supported transports.
+ * "git push" via transport-helper interface has been updated to
+ allow forced ref updates in a way similar to the natively
+ supported transports.
* The "simple" mode is the default for "git push".
* The progress indicators from various time-consuming commands have
been marked for i18n/l10n.
- * "git notes -C <blob>" diagnoses an attempt to use an object that
- is not a blob as an error.
+ * "git notes -C <blob>" diagnoses as an error an attempt to use an
+ object that is not a blob.
* "git config" learned to read from the standard input when "-" is
given as the value to its "--file" parameter (attempting an
- operation to update the configuration in the standard input of
- course is rejected).
+ operation to update the configuration in the standard input is
+ rejected, of course).
* Trailing whitespaces in .gitignore files, unless they are quoted
for fnmatch(3), e.g. "path\ ", are warned and ignored. Strictly
- speaking, this is a backward incompatible change, but very unlikely
+ speaking, this is a backward-incompatible change, but very unlikely
to bite any sane user and adjusting should be obvious and easy.
- * Many commands that create commits, e.g. "pull", "rebase",
- learned to take the --gpg-sign option on the command line.
+ * Many commands that create commits, e.g. "pull" and "rebase",
+ learned to take the "--gpg-sign" option on the command line.
* "git commit" can be told to always GPG sign the resulting commit
- by setting "commit.gpgsign" configuration variable to true (the
- command line option --no-gpg-sign should override it).
+ by setting the "commit.gpgsign" configuration variable to "true"
+ (the command-line option "--no-gpg-sign" should override it).
* "git pull" can be told to only accept fast-forward by setting the
- new "pull.ff" configuration.
+ new "pull.ff" configuration variable.
* "git reset" learned the "-N" option, which does not reset the index
fully for paths the index knows about but the tree-ish the command
* Uses of curl's "multi" interface and "easy" interface do not mix
well when we attempt to reuse outgoing connections. Teach the RPC
- over http code, used in the smart HTTP transport, not to use the
+ over HTTP code, used in the smart HTTP transport, not to use the
"easy" interface.
* The bitmap-index feature from JGit has been ported, which should
* The shell prompt script (in contrib/), when using the PROMPT_COMMAND
interface, used an unsafe construct when showing the branch name in
$PS1.
- (merge 8976500 rh/prompt-pcmode-avoid-eval-on-refname later to maint).
-
- * The remote-helper interface to fast-import/fast-export via the
- transport-helper has been tightened to avoid leaving the import
- marks file from a failed/crashed run, as such a file that is out of
- sync with the reality confuses a later invocation of itself.
+ (merge 1e4119c8 rh/prompt-pcmode-avoid-eval-on-refname later to maint).
- * "git rebase" used a POSIX shell construct FreeBSD /bin/sh does not
+ * "git rebase" used a POSIX shell construct FreeBSD's /bin/sh does not
work well with.
(merge 8cd6596 km/avoid-non-function-return-in-rebase later to maint).
* zsh prompt (in contrib/) leaked unnecessary error messages.
- * bash completion (in contrib/) did not complete the refs and remotes
+ * Bash completion (in contrib/) did not complete the refs and remotes
correctly given "git pu<TAB>" when "pu" is aliased to "push".
- * Some more Unicode codepoints defined in Unicode 6.3 as having zero
- width have been taught to our display column counting logic.
+ * Some more Unicode code points, defined in Unicode 6.3 as having zero
+ width, have been taught to our display column counting logic.
(merge d813ab9 tb/unicode-6.3-zero-width later to maint).
* Some tests used shell constructs that did not work well on FreeBSD
* "git diff --no-index -Mq a b" fell into an infinite loop.
(merge ad1c3fb jc/fix-diff-no-index-diff-opt-parse later to maint).
- * "git fetch --prune", when the right-hand-side of multiple fetch
+ * "git fetch --prune", when the right-hand side of multiple fetch
refspecs overlap (e.g. storing "refs/heads/*" to
"refs/remotes/origin/*", while storing "refs/frotz/*" to
"refs/remotes/origin/fr/*"), aggressively thought that lack of
(merge 3c3e6f5 rr/doc-merge-strategies later to maint).
* Serving objects from a shallow repository needs to write a
- new file to hold the temporary shallow boundaries but it was not
+ new file to hold the temporary shallow boundaries, but it was not
cleaned when we exit due to die() or a signal.
(merge 7839632 jk/shallow-update-fix later to maint).
".git" tells us where it is.
(merge fcfec8b da/difftool-git-files later to maint).
- * "git push" did not pay attention to branch.*.pushremote if it is
- defined earlier than remote.pushdefault; the order of these two
+ * "git push" did not pay attention to "branch.*.pushremote" if it is
+ defined earlier than "remote.pushdefault"; the order of these two
variables in the configuration file should not matter, but it did
by mistake.
(merge 98b406f jk/remote-pushremote-config-reading later to maint).
- * Codepaths that parse timestamps in commit objects have been
+ * Code paths that parse timestamps in commit objects have been
tightened.
(merge f80d1f9 jk/commit-dates-parsing-fix later to maint).
* "git diff --external-diff" incorrectly fed the submodule directory
- in the working tree to the external diff driver when it knew it is
- the same as one of the versions being compared.
+ in the working tree to the external diff driver when it knew that it
+ is the same as one of the versions being compared.
(merge aba4727 tr/diff-submodule-no-reuse-worktree later to maint).
* "git reset" needs to refresh the index when working in a working
* "git check-attr" when working on a repository with a working tree
did not work well when the working tree was specified via the
- --work-tree (and obviously with --git-dir) option.
+ "--work-tree" (and obviously with "--git-dir") option.
(merge cdbf623 jc/check-attr-honor-working-tree later to maint).
* "merge-recursive" was broken in 1.7.7 era and stopped working in
involved. This has been corrected.
(merge 6e2068a bk/refresh-missing-ok-in-merge-recursive later to maint.)
- * "git rev-parse" was loose in rejecting command line arguments
+ * "git rev-parse" was loose in rejecting command-line arguments
that do not make sense, e.g. "--default" without the required
value for that option.
(merge a43219f ds/rev-parse-required-args later to maint.)
- * include.path variable (or any variable that expects a path that
+ * "include.path" variable (or any variable that expects a path that
can use ~username expansion) in the configuration file is not a
boolean, but the code failed to check it.
(merge 67beb60 jk/config-path-include-fix later to maint.)
the pathspec is given as an absolute pathname (which is a
practice not particularly encouraged) that points at a symbolic
link in the working tree.
- (merge later 655ee9e mw/symlinks to maint.)
+ (merge 6127ff6 mw/symlinks later to maint.)
* "git diff --quiet -- pathspec1 pathspec2" sometimes did not return
- correct status value.
+ the correct status value.
(merge f34b205 nd/diff-quiet-stat-dirty later to maint.)
* Attempting to deepen a shallow repository by fetching over smart
- HTTP transport failed in the protocol exchange, when no-done
+ HTTP transport failed in the protocol exchange, when the no-done
extension was used. The fetching side waited for the list of
- shallow boundary commits after the sending end stopped talking to
+ shallow boundary commits after the sending side stopped talking to
it.
(merge 0232852 nd/http-fetch-shallow-fix later to maint.)
* Allow "git cmd path/", when the 'path' is where a submodule is
bound to the top-level working tree, to match 'path', despite the
extra and unnecessary trailing slash (such a slash is often
- given by command line completion).
+ given by command-line completion).
(merge 2e70c01 nd/submodule-pathspec-ending-with-slash later to maint.)
* Documentation and in-code comments had many instances of mistaken
--- /dev/null
+Git v2.1 Release Notes
+======================
+
+Backward compatibility notes
+----------------------------
+
+ * The default value we give to the environment variable LESS has been
+ changed from "FRSX" to "FRX", losing "S" (chop long lines instead
+ of wrapping). Existing users who prefer not to see line-wrapped
+ output may want to set
+
+ $ git config core.pager "less -S"
+
+ to restore the traditional behaviour. It is expected that people
+ find output from the most subcommands easier to read with the new
+ default, except for "blame" which tends to produce really long
+ lines. To override the new default only for "git blame", you can
+ do this:
+
+ $ git config pager.blame "less -S"
+
+ * A few disused directories in contrib/ have been retired.
+
+
+Updates since v2.0
+------------------
+
+UI, Workflows & Features
+
+ * Since the very beginning of Git, we gave the LESS environment a
+ default value "FRSX" when we spawn "less" as the pager. "S" (chop
+ long lines instead of wrapping) has been removed from this default
+ set of options, because it is more or less a personal taste thing,
+ as opposed to others that have good justifications (i.e. "R" is
+ very much justified because many kinds of output we produce are
+ colored and "FX" is justified because output we produce is often
+ shorter than a page).
+
+ * The logic and data used to compute the display width needed for
+ UTF-8 strings have been updated to match Unicode 6.3 better.
+
+ * "git commit --date=<date>" option learned to read from more
+ timestamp formats, including "--date=now".
+
+ * The `core.commentChar` configuration variable is used to specify a
+ custom comment character other than the default "#" to be used in
+ the commit log editor. This can be set to `auto` to attempt to
+ choose a different character that does not conflict with what
+ already starts a line in the message being edited for cases like
+ "git commit --amend".
+
+ * "git grep" learned grep.fullname configuration variable to force
+ "--full-name" to be default. This may cause regressions on
+ scripted users that do not expect this new behaviour.
+
+ * "git imap-send" learned to ask the credential helper for auth
+ material.
+
+ * "git merge" without argument, even when there is an upstream
+ defined for the current branch, refused to run until
+ merge.defaultToUpstream is set to true. Flip the default of that
+ configuration variable to true.
+
+ * "git mergetool" learned to drive the vimdiff3 backend.
+
+ * mergetool.prompt used to default to 'true', always asking "do you
+ really want to run the tool on this path?". Among the two
+ purposes this prompt serves, ignore the use case to confirm that
+ the user wants to view particular path with the named tool, and
+ redefine the meaning of the prompt only to confirm the choice of
+ the tool made by the autodetection (for those who configured the
+ tool explicitly, the prompt shown for the latter purpose is
+ simply annoying).
+
+ Strictly speaking, this is a backward incompatible change and the
+ users need to explicitly set the variable to 'true' if they want
+ to resurrect the now-ignored use case.
+
+ * "git svn" learned to cope with malformed timestamps with only one
+ digit in the hour part, e.g. 2014-01-07T5:01:02.048176Z, emitted
+ by some broken subversion server implementations.
+
+ * "git tag" when editing the tag message shows the name of the tag
+ being edited as a comment in the editor.
+
+
+Performance, Internal Implementation, etc.
+
+ * Build procedure for 'subtree' (in contrib/) has been cleaned up.
+
+ * The `core.deltabasecachelimit` used to default to 16 MiB , but this
+ proved to be too small, and has been bumped to 96 MiB.
+
+ * "git blame" has been optimized greatly by reorganising the data
+ structure that is used to keep track of the work to be done.
+
+ * "git diff" that compares 3-or-more trees (e.g. parents and the
+ result of a merge) have been optimized.
+
+ * The API to update/delete references are being converted to handle
+ updates to multiple references in a transactional way. As an
+ example, "update-ref --stdin [-z]" has been updated to use this
+ API.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.0
+----------------
+
+Unless otherwise noted, all the fixes since v2.0 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * We used to unconditionally disable the pager in the pager process
+ we spawn to feed out output, but that prevented people who want to
+ run "less" within "less" from doing so.
+ (merge c0459ca je/pager-do-not-recurse later to maint).
+
+ * Tools that read diagnostic output in our standard error stream do
+ not want to see terminal control sequence (e.g. erase-to-eol).
+ Detect them by checking if the standard error stream is connected
+ to a tty.
+ (merge 38de156 mn/sideband-no-ansi later to maint).
+
+ * Mishandling of patterns in .gitignore that has trailing SPs quoted
+ with backslashes (e.g. ones that end with "\ ") have been
+ corrected.
+ (merge e61a6c1 pb/trim-trailing-spaces later to maint).
+
+ * "--ignore-space-change" option of "git apply" ignored the spaces
+ at the beginning of line too aggressively, which is inconsistent
+ with the option of the same name "diff" and "git diff" have.
+ (merge 14d3bb4 jc/apply-ignore-whitespace later to maint).
+
+ * "git blame" miscounted number of columns needed to show localized
+ timestamps, resulting in jaggy left-side-edge of the source code
+ lines in its output.
+ (merge dd75553 jx/blame-align-relative-time later to maint).
+
+ * "git blame" assigned the blame to the copy in the working-tree if
+ the repository is set to core.autocrlf=input and the file used CRLF
+ line endings.
+ (merge 4d4813a bc/blame-crlf-test later to maint).
+
+ * "git commit --allow-empty-messag -C $commit" did not work when the
+ commit did not have any log message.
+ (merge 076cbd6 jk/commit-C-pick-empty later to maint).
+
+ * "git grep -O" to show the lines that hit in the pager did not work
+ well with case insensitive search. We now spawn "less" with its
+ "-I" option when it is used as the pager (which is the default).
+ (merge f7febbe sk/spawn-less-case-insensitively-from-grep-O-i later to maint).
+
+ * We used to disable threaded "git index-pack" on platforms without
+ thread-safe pread(); use a different workaround for such
+ platforms to allow threaded "git index-pack".
+ (merge 3953949 nd/index-pack-one-fd-per-thread later to maint).
+
+ * The error reporting from "git index-pack" has been improved to
+ distinguish missing objects from type errors.
+ (merge 77583e7 jk/index-pack-report-missing later to maint).
+
+ * "git mailinfo" used to read beyond the end of header string while
+ parsing an incoming e-mail message to extract the patch.
+ (merge b1a013d rs/mailinfo-header-cmp later to maint).
+
+ * On a case insensitive filesystem, merge-recursive incorrectly
+ deleted the file that is to be renamed to a name that is the same
+ except for case differences.
+ (merge baa37bf dt/merge-recursive-case-insensitive later to maint).
+
+ * "git rerere forget" did not work well when merge.conflictstyle
+ was set to a non-default value.
+ (merge de3d8bb fc/rerere-conflict-style later to maint).
+
+ * "git log --exclude=<glob> --all | git shortlog" worked as expected,
+ but "git shortlog --exclude=<glob> --all", which is supposed to be
+ identical to the above pipeline, was not accepted at the command
+ line argument parser level.
+ (merge eb07774 jc/shortlog-ref-exclude later to maint).
+
+ * "git show -s" (i.e. show log message only) used to incorrectly emit
+ an extra blank line after a merge commit.
+ (merge ad2f725 mk/show-s-no-extra-blank-line-for-merges later to maint).
+
+ * "git status", even though it is a read-only operation, tries to
+ update the index with refreshed lstat(2) info to optimize future
+ accesses to the working tree opportunistically, but this could
+ race with a "read-write" operation that modify the index while it
+ is running. Detect such a race and avoid overwriting the index.
+ (merge 426ddee ym/fix-opportunistic-index-update-race later to maint).
+
+ * "git update-index --cacheinfo" in 2.0 release crashed on a
+ malformed command line.
+ (merge c8e1ee4 jc/rev-parse-argh-dashed-multi-words later to maint).
+
+ * The mode to run tests with HTTP server tests disabled was broken.
+ (merge afa53fe na/no-http-test-in-the-middle later to maint).
core.worktree::
Set the path to the root of the working tree.
This can be overridden by the GIT_WORK_TREE environment
- variable and the '--work-tree' command line option.
+ variable and the '--work-tree' command-line option.
The value can be an absolute path or relative to the path to
the .git directory, which is either specified by --git-dir
or GIT_DIR, or automatically discovered.
to avoid unpacking and decompressing frequently used base
objects multiple times.
+
-Default is 16 MiB on all platforms. This should be reasonable
+Default is 96 MiB on all platforms. This should be reasonable
for all users/operating systems, except on the largest projects.
You probably do not need to adjust this value.
+
environment variable. If not set, fall back to the value of the
'SSH_ASKPASS' environment variable or, failing that, a simple password
prompt. The external program shall be given a suitable prompt as
- command line argument and write the password on its STDOUT.
+ command-line argument and write the password on its STDOUT.
core.attributesfile::
In addition to '.gitattributes' (per-directory) and
messages consider a line that begins with this character
commented, and removes them after the editor returns
(default '#').
++
+If set to "auto", `git-commit` would select a character that is not
+the beginning character of any line in existing commit messages.
sequence.editor::
Text editor used by `git rebase -i` for editing the rebase instruction file.
configuration, then `$PAGER`, and then the default chosen at
compile time (usually 'less').
+
-When the `LESS` environment variable is unset, Git sets it to `FRSX`
+When the `LESS` environment variable is unset, Git sets it to `FRX`
(if `LESS` environment variable is set, Git does not change it at
all). If you want to selectively override Git's default setting
-for `LESS`, you can set `core.pager` to e.g. `less -+S`. This will
+for `LESS`, you can set `core.pager` to e.g. `less -S`. This will
be passed to the shell by Git, which will translate the final
-command to `LESS=FRSX less -+S`. The environment tells the command
-to set the `S` option to chop long lines but the command line
-resets it to the default to fold long lines.
+command to `LESS=FRX less -S`. The environment does not set the
+`S` option but the command line does, instructing less to truncate
+long lines. Similarly, setting `core.pager` to `less -+F` will
+deactivate the `F` option specified by the environment from the
+command-line, deactivating the "quit if one screen" behavior of
+`less`. One can specifically activate some flags for particular
+commands: for example, setting `pager.blame` to `less -S` enables
+line truncation only for `git blame`.
+
Likewise, when the `LV` environment variable is unset, Git sets it
to `-c`. You can override this setting by exporting `LV` with
gpg.program::
Use this custom program instead of "gpg" found on $PATH when
making or verifying a PGP signature. The program must support the
- same command line interface as GPG, namely, to verify a detached
+ same command-line interface as GPG, namely, to verify a detached
signature, "gpg --verify $file - <$signature" is run, and the
program is expected to signal a good signature by exiting with
code 0, and to generate an ascii-armored detached signature, the
submodules when `diff.ignoreSubmodules` is set to 'all' or only
for those submodules where `submodule.<name>.ignore=all`. To
also view the summary for ignored submodules you can either use
- the --ignore-submodules=dirty command line option or the 'git
+ the --ignore-submodules=dirty command-line option or the 'git
submodule summary' command, which shows a similar output but does
not honor these settings.
submodule.<name>.fetchRecurseSubmodules::
This option can be used to control recursive fetching of this
submodule. It can be overridden by using the --[no-]recurse-submodules
- command line option to "git fetch" and "git pull".
+ command-line option to "git fetch" and "git pull".
This setting will override that from in the linkgit:gitmodules[5]
file.
this setting when reporting uncommitted changes. Setting it to
'all' disables the submodule summary normally shown by 'git commit'
and 'git status' when 'status.submodulesummary' is set unless it is
- overridden by using the --ignore-submodules command line option.
+ overridden by using the --ignore-submodules command-line option.
The 'git submodule' commands are not affected by this setting.
diff.mnemonicprefix::
`view` may also be used as a synonym for `visualize`.
If the 'DISPLAY' environment variable is not set, 'git log' is used
-instead. You can also give command line options such as `-p` and
+instead. You can also give command-line options such as `-p` and
`--stat`.
------------
configuration file. Note that this also affects options like '--replace-all'
and '--unset'. *'git config' will only ever change one file at a time*.
-You can override these rules either by command line options or by environment
+You can override these rules either by command-line options or by environment
variables. The '--global' and the '--system' options will limit the file used
to the global or system-wide file respectively. The GIT_CONFIG environment
variable has a similar effect, but you can specify any filename you want.
--forbid-override=<service>::
Allow/forbid overriding the site-wide default with per
repository configuration. By default, all the services
- are overridable.
+ may be overridden.
--[no-]informative-errors::
When informative errors are turned on, git-daemon will report
Every time a client connects, first run an external command
specified by the <path> with service name (e.g. "upload-pack"),
path to the repository, hostname (%H), canonical hostname
- (%CH), ip address (%IP), and tcp port (%P) as its command line
+ (%CH), IP address (%IP), and TCP port (%P) as its command-line
arguments. The external command can decide to decline the
service by exiting with a non-zero status (or to allow it by
exiting with a zero status). It can also look at the $REMOTE_ADDR
--------
These services can be globally enabled/disabled using the
-command line options of this command. If a finer-grained
+command-line options of this command. If finer-grained
control is desired (e.g. to allow 'git archive' to be run
against only in a few selected repositories the daemon serves),
the per-repository configuration file can be used to enable or
~~~~~~~~~~~~
The following date formats are supported. A frontend should select
the format it will use for this import by passing the format name
-in the \--date-format=<fmt> command line option.
+in the \--date-format=<fmt> command-line option.
`raw`::
This is the Git native format and is `<time> SP <offutc>`.
`done`::
Marks the end of the stream. This command is optional
unless the `done` feature was requested using the
- `--done` command line option or `feature done` command.
+ `--done` command-line option or `feature done` command.
`cat-blob`::
Causes fast-import to print a blob in 'cat-file --batch'
of bytes, except `LT`, `GT` and `LF`. `<name>` is typically UTF-8 encoded.
The time of the change is specified by `<when>` using the date format
-that was selected by the \--date-format=<fmt> command line option.
+that was selected by the \--date-format=<fmt> command-line option.
See ``Date Formats'' above for the set of supported formats, and
their syntax.
feature commands), to give an option command after any non-option
command is an error.
-The following commandline options change import semantics and may therefore
+The following command-line options change import semantics and may therefore
not be passed as option:
* date-format
If the `done` feature is not in use, treated as if EOF was read.
This can be used to tell fast-import to finish early.
-If the `--done` command line option or `feature done` command is
+If the `--done` command-line option or `feature done` command is
in use, the `done` command is mandatory and marks the end of the
stream.
option is ignored when the 'grep.patternType' option is set to a value
other than 'default'.
+grep.fullName::
+ If set to true, enable '--full-name' option by default.
+
OPTIONS
-------
help.format
~~~~~~~~~~~
-If no command line option is passed, the 'help.format' configuration
+If no command-line option is passed, the 'help.format' configuration
variable will be checked. The following values are supported for this
-variable; they make 'git help' behave as their corresponding command
+variable; they make 'git help' behave as their corresponding command-
line option:
* "man" corresponds to '-m|--man',
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also
-be checked if the 'web' format is chosen (either by command line
+be checked if the 'web' format is chosen (either by command-line
option or configuration variable). See '-w|--web' in the OPTIONS
section above and linkgit:git-web{litdd}browse[1].
man.viewer
~~~~~~~~~~
-The 'man.viewer' config variable will be checked if the 'man' format
-is chosen. The following values are currently supported:
+The 'man.viewer' configuration variable will be checked if the 'man'
+format is chosen. The following values are currently supported:
* "man": use the 'man' program as usual,
* "woman": use 'emacsclient' to launch the "woman" mode in emacs
viewer = woman
------------------------------------------------
-will try to use konqueror first. But this may fail (for example if
+will try to use konqueror first. But this may fail (for example, if
DISPLAY is not set) and in that case emacs' woman mode will be tried.
If everything fails, or if no viewer is configured, the viewer specified
These exclude patterns come from these places, in order:
- 1. The command line flag --exclude=<pattern> specifies a
+ 1. The command-line flag --exclude=<pattern> specifies a
single pattern. Patterns are ordered in the same order
they appear in the command line.
- 2. The command line flag --exclude-from=<file> specifies a
+ 2. The command-line flag --exclude-from=<file> specifies a
file containing a list of patterns. Patterns are ordered
in the same order they appear in the file.
- 3. The command line flag --exclude-per-directory=<name> specifies
+ 3. The command-line flag --exclude-per-directory=<name> specifies
a name of the file in each directory 'git ls-files'
examines, normally `.gitignore`. Files in deeper
directories take precedence. Patterns are ordered in the
Specifying more than one commit will create a merge with
more than two parents (affectionately called an Octopus merge).
+
-If no commit is given from the command line, and if `merge.defaultToUpstream`
-configuration variable is set, merge the remote-tracking branches
-that the current branch is configured to use as its upstream.
+If no commit is given from the command line, merge the remote-tracking
+branches that the current branch is configured to use as its upstream.
See also the configuration section of this manual page.
--no-prompt::
Don't prompt before each invocation of the merge resolution
program.
+ This is the default if the merge resolution program is
+ explicitly specified with the `--tool` option or with the
+ `merge.tool` configuration variable.
--prompt::
- Prompt before each invocation of the merge resolution program.
- This is the default behaviour; the option is provided to
- override any configuration settings.
+ Prompt before each invocation of the merge resolution program
+ to give the user a chance to skip the path.
TEMPORARY FILES
---------------
SYNOPSIS
--------
[verse]
-'git patch-id' < <patch>
+'git patch-id' [--stable | --unstable] < <patch>
DESCRIPTION
-----------
-A "patch ID" is nothing but a SHA-1 of the diff associated with a patch, with
-whitespace and line numbers ignored. As such, it's "reasonably stable", but at
-the same time also reasonably unique, i.e., two patches that have the same "patch
-ID" are almost guaranteed to be the same thing.
+A "patch ID" is nothing but a sum of SHA-1 of the file diffs associated with a
+patch, with whitespace and line numbers ignored. As such, it's "reasonably
+stable", but at the same time also reasonably unique, i.e., two patches that
+have the same "patch ID" are almost guaranteed to be the same thing.
IOW, you can use this thing to look for likely duplicate commits.
OPTIONS
-------
+
+--stable::
+ Use a "stable" sum of hashes as the patch ID. With this option:
+ - Reordering file diffs that make up a patch does not affect the ID.
+ In particular, two patches produced by comparing the same two trees
+ with two different settings for "-O<orderfile>" result in the same
+ patch ID signature, thereby allowing the computed result to be used
+ as a key to index some meta-information about the change between
+ the two trees;
+
+ - Result is different from the value produced by git 1.9 and older
+ or produced when an "unstable" hash (see --unstable below) is
+ configured - even when used on a diff output taken without any use
+ of "-O<orderfile>", thereby making existing databases storing such
+ "unstable" or historical patch-ids unusable.
+
+ This is the default if patchid.stable is set to true.
+
+--unstable::
+ Use an "unstable" hash as the patch ID. With this option,
+ the result produced is compatible with the patch-id value produced
+ by git 1.9 and older. Users with pre-existing databases storing
+ patch-ids produced by git 1.9 and older (who do not deal with reordered
+ patches) may want to use this option.
+
+ This is the default.
+
<patch>::
The diff to create the ID of.
you are trying to merge (stage 2 and 3 respectively).
The order of stages 1, 2 and 3 (hence the order of three
-<tree-ish> command line arguments) are significant when you
+<tree-ish> command-line arguments) are significant when you
start a 3-way merge with an index file that is already
populated. Here is an outline of how the algorithm works:
--------
[verse]
'git replace' [-f] <object> <replacement>
+'git replace' [-f] --edit <object>
'git replace' -d <object>...
'git replace' [--format=<format>] [-l [<pattern>]]
--delete::
Delete existing replace refs for the given objects.
+--edit <object>::
+ Edit an object's content interactively. The existing content
+ for <object> is pretty-printed into a temporary file, an
+ editor is launched on the file, and the result is parsed to
+ create a new object of the same type as <object>. A
+ replacement ref is then created to replace <object> with the
+ newly created object. See linkgit:git-var[1] for details about
+ how the editor will be chosen.
+
-l <pattern>::
--list <pattern>::
List replace refs for objects that match the given pattern (or
linkgit:git-filter-branch[1], linkgit:git-hash-object[1] and
linkgit:git-rebase[1], among other git commands, can be used to create
-replacement objects from existing objects.
+replacement objects from existing objects. The `--edit` option can
+also be used with 'git replace' to create a replacement object by
+editing an existing object.
If you want to replace many blobs, trees or commits that are part of a
string of commits, you may just want to create a replacement string of
linkgit:git-rebase[1]
linkgit:git-tag[1]
linkgit:git-branch[1]
+linkgit:git-commit[1]
+linkgit:git-var[1]
linkgit:git[1]
GIT
last case, any format accepted by linkgit:git-format-patch[1] can
be passed to git send-email.
-The header of the email is configurable by command line options. If not
+The header of the email is configurable via command-line options. If not
specified on the command line, the user will be prompted with a ReadLine
enabled interface to provide the necessary information.
When '--compose' is used, git send-email will use the From, Subject, and
In-Reply-To headers specified in the message. If the body of the message
(what you type after the headers and a blank line) only contains blank
-(or Git: prefixed) lines the summary won't be sent, but From, Subject,
+(or Git: prefixed) lines, the summary won't be sent, but From, Subject,
and In-Reply-To headers will be used unless they are removed.
+
Missing From or In-Reply-To headers will be prompted for.
--from=<address>::
Specify the sender of the emails. If not specified on the command line,
the value of the 'sendemail.from' configuration option is used. If
- neither the command line option nor 'sendemail.from' are set, then the
+ neither the command-line option nor 'sendemail.from' are set, then the
user will be prompted for the value. The default for the prompt will be
the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not
set, as returned by "git var -l".
[verse]
config key: svn-remote.<name>.ignore-paths
+
-If the ignore-paths config key is set and the command line option is
-also given, both regular expressions will be used.
+If the ignore-paths configuration key is set, and the command-line
+option is also given, both regular expressions will be used.
+
Examples:
+
option SP <opt> LF
Quote fields containing whitespace as if they were strings in C source
-code. Alternatively, use `-z` to specify commands without quoting:
+code; i.e., surrounded by double-quotes and with backslash escapes.
+Use 40 "0" characters or the empty string to specify a zero value. To
+specify a missing value, omit the value and its preceding SP entirely.
+
+Alternatively, use `-z` to specify in NUL-terminated format, without
+quoting:
update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
create SP <ref> NUL <newvalue> NUL
verify SP <ref> NUL [<oldvalue>] NUL
option SP <opt> NUL
-Lines of any other format or a repeated <ref> produce an error.
-Command meanings are:
+In this format, use 40 "0" to specify a zero value, and use the empty
+string to specify a missing value.
+
+In either format, values can be specified in any form that Git
+recognizes as an object name. Commands in any other format or a
+repeated <ref> produce an error. Command meanings are:
update::
Set <ref> to <newvalue> after verifying <oldvalue>, if given.
The only valid option is `no-deref` to avoid dereferencing
a symbolic ref.
-Use 40 "0" or the empty string to specify a zero value, except that
-with `-z` an empty <oldvalue> is considered missing.
-
If all <ref>s can be locked with matching <oldvalue>s
simultaneously, all modifications are performed. Otherwise, no
modifications are performed. Note that while each individual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The web browser can be specified using a configuration variable passed
-with the -c (or --config) command line option, or the 'web.browser'
+with the -c (or --config) command-line option, or the 'web.browser'
configuration variable if the former is not used.
browser.<tool>.path
Note about konqueror
--------------------
-When 'konqueror' is specified by a command line option or a
+When 'konqueror' is specified by a command-line option or a
configuration variable, we launch 'kfmclient' to try to open the HTML
man page on an already opened konqueror in a new tab if possible.
After you mastered the basic concepts, you can come back to this
page to learn what commands Git offers. You can learn more about
individual Git commands with "git help command". linkgit:gitcli[7]
-manual page gives you an overview of the command line command syntax.
+manual page gives you an overview of the command-line command syntax.
Formatted and hyperlinked version of the latest Git documentation
can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`.
============
You are reading the documentation for the latest (possibly
-unreleased) version of Git, that is available from 'master'
+unreleased) version of Git, that is available from the 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.9.3/git.html[documentation for release 1.9.3]
+* link:v2.0.0/git.html[documentation for release 2.0]
* release notes for
+ link:RelNotes/2.0.0.txt[2.0.0].
+
+* link:v1.9.4/git.html[documentation for release 1.9.4]
+
+* release notes for
+ link:RelNotes/1.9.4.txt[1.9.4],
link:RelNotes/1.9.3.txt[1.9.3],
link:RelNotes/1.9.2.txt[1.9.2],
link:RelNotes/1.9.1.txt[1.9.1],
'GIT_WORK_TREE'::
Set the path to the root of the working tree.
- This can also be controlled by the '--work-tree' command line
+ This can also be controlled by the '--work-tree' command-line
option and the core.worktree configuration variable.
'GIT_NAMESPACE'::
'GIT_ASKPASS'::
If this environment variable is set, then Git commands which need to
acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
- will call this program with a suitable prompt as command line argument
+ will call this program with a suitable prompt as command-line argument
and read the password from its STDOUT. See also the 'core.askpass'
option in linkgit:git-config[1].
NAME
----
-gitcli - Git command line interface and conventions
+gitcli - Git command-line interface and conventions
SYNOPSIS
--------
Here are the rules regarding the "flags" that you should follow when you are
scripting Git:
- * it's preferred to use the non dashed form of Git commands, which means that
+ * it's preferred to use the non-dashed form of Git commands, which means that
you should prefer `git foo` to `git-foo`.
* splitting short options to separate words (prefer `git foo -a -b`
to `git foo -ab`, the latter may not even work).
- * when a command line option takes an argument, use the 'stuck' form. In
+ * when a command-line option takes an argument, use the 'stuck' form. In
other words, write `git foo -oArg` instead of `git foo -o Arg` for short
options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`
for long options. An option that takes optional option-argument must be
Magic Options
~~~~~~~~~~~~~
Commands which have the enhanced option parser activated all understand a
-couple of magic command line options:
+couple of magic command-line options:
-h::
gives a pretty printed usage of the command.
gitk generally only understands options with arguments in the
'sticked' form (see linkgit:gitcli[7]) due to limitations in the
-command line parser.
+command-line parser.
rev-list options and arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$feature{'snapshot'}{'override'} = 1;
If you allow overriding for the snapshot feature, you can specify which
-snapshot formats are globally disabled. You can also add any command line
+snapshot formats are globally disabled. You can also add any command-line
options you want (such as setting the compression level). For instance, you
can disable Zip compressed snapshots and set *gzip*(1) to run at level 6 by
adding the following lines to your gitweb configuration file:
--- /dev/null
+From: Junio C Hamano <gitster@pobox.com>
+Date: Wed, 07 May 2014 13:15:39 -0700
+Subject: Beginner question on "Pull is mostly evil"
+Abstract: This how-to explains a method for keeping a
+ project's history correct when using git pull.
+Content-type: text/asciidoc
+
+Keep authoritative canonical history correct with git pull
+==========================================================
+
+Sometimes a new project integrator will end up with project history
+that appears to be "backwards" from what other project developers
+expect. This howto presents a suggested integration workflow for
+maintaining a central repository.
+
+Suppose that that central repository has this history:
+
+------------
+ ---o---o---A
+------------
+
+which ends at commit `A` (time flows from left to right and each node
+in the graph is a commit, lines between them indicating parent-child
+relationship).
+
+Then you clone it and work on your own commits, which leads you to
+have this history in *your* repository:
+
+------------
+ ---o---o---A---B---C
+------------
+
+Imagine your coworker did the same and built on top of `A` in *his*
+repository in the meantime, and then pushed it to the
+central repository:
+
+------------
+ ---o---o---A---X---Y---Z
+------------
+
+Now, if you `git push` at this point, because your history that leads
+to `C` lacks `X`, `Y` and `Z`, it will fail. You need to somehow make
+the tip of your history a descendant of `Z`.
+
+One suggested way to solve the problem is "fetch and then merge", aka
+`git pull`. When you fetch, your repository will have a history like
+this:
+
+------------
+ ---o---o---A---B---C
+ \
+ X---Y---Z
+------------
+
+Once you run merge after that, while still on *your* branch, i.e. `C`,
+you will create a merge `M` and make the history look like this:
+
+------------
+ ---o---o---A---B---C---M
+ \ /
+ X---Y---Z
+------------
+
+`M` is a descendant of `Z`, so you can push to update the central
+repository. Such a merge `M` does not lose any commit in both
+histories, so in that sense it may not be wrong, but when people want
+to talk about "the authoritative canonical history that is shared
+among the project participants", i.e. "the trunk", they often view
+it as "commits you see by following the first-parent chain", and use
+this command to view it:
+
+------------
+ $ git log --first-parent
+------------
+
+For all other people who observed the central repository after your
+coworker pushed `Z` but before you pushed `M`, the commit on the trunk
+used to be `o-o-A-X-Y-Z`. But because you made `M` while you were on
+`C`, `M`'s first parent is `C`, so by pushing `M` to advance the
+central repository, you made `X-Y-Z` a side branch, not on the trunk.
+
+You would rather want to have a history of this shape:
+
+------------
+ ---o---o---A---X---Y---Z---M'
+ \ /
+ B-----------C
+------------
+
+so that in the first-parent chain, it is clear that the project first
+did `X` and then `Y` and then `Z` and merged a change that consists of
+two commits `B` and `C` that achieves a single goal. You may have
+worked on fixing the bug #12345 with these two patches, and the merge
+`M'` with swapped parents can say in its log message "Merge
+fix-bug-12345". Having a way to tell `git pull` to create a merge
+but record the parents in reverse order may be a way to do so.
+
+Note that I said "achieves a single goal" above, because this is
+important. "Swapping the merge order" only covers a special case
+where the project does not care too much about having unrelated
+things done on a single merge but cares a lot about first-parent
+chain.
+
+There are multiple schools of thought about the "trunk" management.
+
+ 1. Some projects want to keep a completely linear history without any
+ merges. Obviously, swapping the merge order would not match their
+ taste. You would need to flatten your history on top of the
+ updated upstream to result in a history of this shape instead:
++
+------------
+ ---o---o---A---X---Y---Z---B---C
+------------
++
+with `git pull --rebase` or something.
+
+ 2. Some projects tolerate merges in their history, but do not worry
+ too much about the first-parent order, and allow fast-forward
+ merges. To them, swapping the merge order does not hurt, but
+ it is unnecessary.
+
+ 3. Some projects want each commit on the "trunk" to do one single
+ thing. The output of `git log --first-parent` in such a project
+ would show either a merge of a side branch that completes a single
+ theme, or a single commit that completes a single theme by itself.
+ If your two commits `B` and `C` (or they may even be two groups of
+ commits) were solving two independent issues, then the merge `M'`
+ we made in the earlier example by swapping the merge order is
+ still not up to the project standard. It merges two unrelated
+ efforts `B` and `C` at the same time.
+
+For projects in the last category (Git itself is one of them),
+individual developers would want to prepare a history more like
+this:
+
+------------
+ C0--C1--C2 topic-c
+ /
+ ---o---o---A master
+ \
+ B0--B1--B2 topic-b
+------------
+
+That is, keeping separate topics on separate branches, perhaps like
+so:
+
+------------
+ $ git clone $URL work && cd work
+ $ git checkout -b topic-b master
+ $ ... work to create B0, B1 and B2 to complete one theme
+ $ git checkout -b topic-c master
+ $ ... same for the theme of topic-c
+------------
+
+And then
+
+------------
+ $ git checkout master
+ $ git pull --ff-only
+------------
+
+would grab `X`, `Y` and `Z` from the upstream and advance your master
+branch:
+
+------------
+ C0--C1--C2 topic-c
+ /
+ ---o---o---A---X---Y---Z master
+ \
+ B0--B1--B2 topic-b
+------------
+
+And then you would merge these two branches separately:
+
+------------
+ $ git merge topic-b
+ $ git merge topic-c
+------------
+
+to result in
+
+------------
+ C0--C1---------C2
+ / \
+ ---o---o---A---X---Y---Z---M---N
+ \ /
+ B0--B1-----B2
+------------
+
+and push it back to the central repository.
+
+It is very much possible that while you are merging topic-b and
+topic-c, somebody again advanced the history in the central repository
+to put `W` on top of `Z`, and make your `git push` fail.
+
+In such a case, you would rewind to discard `M` and `N`, update the
+tip of your 'master' again and redo the two merges:
+
+------------
+ $ git reset --hard origin/master
+ $ git pull --ff-only
+ $ git merge topic-b
+ $ git merge topic-c
+------------
+
+The procedure will result in a history that looks like this:
+
+------------
+ C0--C1--------------C2
+ / \
+ ---o---o---A---X---Y---Z---W---M'--N'
+ \ /
+ B0--B1---------B2
+------------
+
+See also http://git-blame.blogspot.com/2013/09/fun-with-first-parent-history.html
Most tests should pass.
-A command line tool to test WebDAV is cadaver. If you prefer GUIs, for
+A command-line tool to test WebDAV is cadaver. If you prefer GUIs, for
example, konqueror can open WebDAV URLs as "webdav://..." or
"webdavs://...".
'<branchname>@\{upstream\}', e.g. 'master@\{upstream\}', '@\{u\}'::
The suffix '@\{upstream\}' to a branchname (short form '<branchname>@\{u\}')
refers to the branch that the branch specified by branchname is set to build on
- top of. A missing branchname defaults to the current one.
+ top of (configured with `branch.<name>.remote` and
+ `branch.<name>.merge`). A missing branchname defaults to the
+ current one.
'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
A suffix '{caret}' to a revision parameter means the first parent of
`argv_array_clear`::
Free all memory associated with the array and return it to the
initial, empty state.
-
-`argv_array_detach`::
- Detach the argv array from the `struct argv_array`, transferring
- ownership of the allocated array and strings.
-
-`argv_array_free_detached`::
- Free the memory allocated by a `struct argv_array` that was later
- detached and is now no longer needed.
where options is the bitwise-or of:
`RUN_SETUP`::
-
- Make sure there is a Git directory to work on, and if there is a
- work tree, chdir to the top of it if the command was invoked
- in a subdirectory. If there is no work tree, no chdir() is
- done.
+ If there is not a Git directory to work on, abort. If there
+ is a work tree, chdir to the top of it if the command was
+ invoked in a subdirectory. If there is no work tree, no
+ chdir() is done.
+
+`RUN_SETUP_GENTLY`::
+ If there is a Git directory, chdir as per RUN_SETUP, otherwise,
+ don't chdir anywhere.
`USE_PAGER`::
-------------
Here's a simple usage example that maps long keys to double values.
-[source,c]
------------
struct hashmap map;
without a path). If the command to run is a git command, set argv[0] to
the command name without the 'git-' prefix and set .git_cmd = 1.
+Note that the ownership of the memory pointed to by .argv stays with the
+caller, but it should survive until `finish_command` completes. If the
+.argv member is NULL, `start_command` will point it at the .args
+`argv_array` (so you may use one or the other, but you must use exactly
+one). The memory in .args will be cleaned up automatically during
+`finish_command` (or during `start_command` when it is unsuccessful).
+
The members .in, .out, .err are used to redirect stdin, stdout,
stderr as follows:
* Related to the contents of the buffer
+`strbuf_trim`::
+
+ Strip whitespace from the beginning and end of a string.
+ Equivalent to performing `strbuf_rtrim()` followed by `strbuf_ltrim()`.
+
`strbuf_rtrim`::
Strip whitespace from the end of a string.
+`strbuf_ltrim`::
+
+ Strip whitespace from the beginning of a string.
+
`strbuf_cmp`::
Compare two buffers. Returns an integer less than, equal to, or greater
C: 0000
The stream is organized into "commands", with each command
-appearing by itself in a pkt-line. Within a command line
+appearing by itself in a pkt-line. Within a command line,
the text leading up to the first space is the command name,
and the remainder of the line to the first LF is the value.
Command lines are terminated with an LF as the last byte of
controls how and what revisions are walked, and more.
The original job of `git rev-parse` is now taken by the function
-`setup_revisions()`, which parses the revisions and the common command line
+`setup_revisions()`, which parses the revisions and the common command-line
options for the revision walker. This information is stored in the struct
-`rev_info` for later consumption. You can do your own command line option
+`rev_info` for later consumption. You can do your own command-line option
parsing after calling `setup_revisions()`. After that, you have to call
`prepare_revision_walk()` for initialization, and then you can get the
commits one by one with the function `get_revision()`.
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.0.0-rc3
+DEF_VER=v2.0.0.GIT
LF='
'
# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
# /foo/bar/include and /foo/bar/lib directories.
#
+# Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.
+#
# Define NO_CURL if you do not have libcurl installed. git-http-fetch and
# git-http-push are not built, and you cannot use http:// and https://
# transports (neither smart nor dumb).
# Define NO_STRUCT_ITIMERVAL if you don't have struct itimerval
# This also implies NO_SETITIMER
#
-# Define NO_THREAD_SAFE_PREAD if your pread() implementation is not
-# thread-safe. (e.g. compat/pread.c or cygwin)
-#
# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is
# generally faster on your platform than accessing the working directory.
#
LIB_H += tree-walk.h
LIB_H += tree.h
LIB_H += unpack-trees.h
+LIB_H += unicode_width.h
LIB_H += url.h
LIB_H += urlmatch.h
LIB_H += userdiff.h
EXTLIBS += -lpcre
endif
+ifdef HAVE_ALLOCA_H
+ BASIC_CFLAGS += -DHAVE_ALLOCA_H
+endif
+
ifdef NO_CURL
BASIC_CFLAGS += -DNO_CURL
REMOTE_CURL_PRIMARY =
ifdef NO_PREAD
COMPAT_CFLAGS += -DNO_PREAD
COMPAT_OBJS += compat/pread.o
- NO_THREAD_SAFE_PREAD = YesPlease
-endif
-ifdef NO_THREAD_SAFE_PREAD
- BASIC_CFLAGS += -DNO_THREAD_SAFE_PREAD
endif
ifdef NO_FAST_WORKING_DIRECTORY
BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
-Documentation/RelNotes/2.0.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.1.0.txt
\ No newline at end of file
}
argv_array_init(array);
}
-
-const char **argv_array_detach(struct argv_array *array, int *argc)
-{
- const char **argv =
- array->argv == empty_argv || array->argc == 0 ? NULL : array->argv;
- if (argc)
- *argc = array->argc;
- argv_array_init(array);
- return argv;
-}
-
-void argv_array_free_detached(const char **argv)
-{
- if (argv) {
- int i;
- for (i = 0; argv[i]; i++)
- free((char **)argv[i]);
- free(argv);
- }
-}
void argv_array_pushl(struct argv_array *, ...);
void argv_array_pop(struct argv_array *);
void argv_array_clear(struct argv_array *);
-const char **argv_array_detach(struct argv_array *array, int *argc);
-void argv_array_free_detached(const char **argv);
#endif /* ARGV_ARRAY_H */
while ((*last2 == '\r') || (*last2 == '\n'))
last2--;
- /* skip leading whitespace */
- while (isspace(*s1) && (s1 <= last1))
- s1++;
- while (isspace(*s2) && (s2 <= last2))
- s2++;
+ /* skip leading whitespaces, if both begin with whitespace */
+ if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) {
+ while (isspace(*s1) && (s1 <= last1))
+ s1++;
+ while (isspace(*s2) && (s2 <= last2))
+ s2++;
+ }
/* early return if both lines are empty */
if ((s1 > last1) && (s2 > last2))
return 1;
/*
* Blame
*
- * Copyright (c) 2006, Junio C Hamano
+ * Copyright (c) 2006, 2014 by its authors
+ * See COPYING for licensing conditions
*/
#include "cache.h"
#include "cache-tree.h"
#include "string-list.h"
#include "mailmap.h"
+#include "mergesort.h"
#include "parse-options.h"
+#include "prio-queue.h"
#include "utf8.h"
#include "userdiff.h"
#include "line-range.h"
*/
struct origin {
int refcnt;
+ /* Record preceding blame record for this blob */
struct origin *previous;
+ /* origins are put in a list linked via `next' hanging off the
+ * corresponding commit's util field in order to make finding
+ * them fast. The presence in this chain does not count
+ * towards the origin's reference count. It is tempting to
+ * let it count as long as the commit is pending examination,
+ * but even under circumstances where the commit will be
+ * present multiple times in the priority queue of unexamined
+ * commits, processing the first instance will not leave any
+ * work requiring the origin data for the second instance. An
+ * interspersed commit changing that would have to be
+ * preexisting with a different ancestry and with the same
+ * commit date in order to wedge itself between two instances
+ * of the same commit in the priority queue _and_ produce
+ * blame entries relevant for it. While we don't want to let
+ * us get tripped up by this case, it certainly does not seem
+ * worth optimizing for.
+ */
+ struct origin *next;
struct commit *commit;
+ /* `suspects' contains blame entries that may be attributed to
+ * this origin's commit or to parent commits. When a commit
+ * is being processed, all suspects will be moved, either by
+ * assigning them to an origin in a different commit, or by
+ * shipping them to the scoreboard's ent list because they
+ * cannot be attributed to a different commit.
+ */
+ struct blame_entry *suspects;
mmfile_t file;
unsigned char blob_sha1[20];
unsigned mode;
+ /* guilty gets set when shipping any suspects to the final
+ * blame list instead of other commits
+ */
+ char guilty;
char path[FLEX_ARRAY];
};
static void origin_decref(struct origin *o)
{
if (o && --o->refcnt <= 0) {
+ struct origin *p, *l = NULL;
if (o->previous)
origin_decref(o->previous);
free(o->file.ptr);
- free(o);
+ /* Should be present exactly once in commit chain */
+ for (p = o->commit->util; p; l = p, p = p->next) {
+ if (p == o) {
+ if (l)
+ l->next = p->next;
+ else
+ o->commit->util = p->next;
+ free(o);
+ return;
+ }
+ }
+ die("internal error in blame::origin_decref");
}
}
/*
* Each group of lines is described by a blame_entry; it can be split
- * as we pass blame to the parents. They form a linked list in the
- * scoreboard structure, sorted by the target line number.
+ * as we pass blame to the parents. They are arranged in linked lists
+ * kept as `suspects' of some unprocessed origin, or entered (when the
+ * blame origin has been finalized) into the scoreboard structure.
+ * While the scoreboard structure is only sorted at the end of
+ * processing (according to final image line number), the lists
+ * attached to an origin are sorted by the target line number.
*/
struct blame_entry {
struct blame_entry *next;
/* the commit that introduced this group into the final image */
struct origin *suspect;
- /* true if the suspect is truly guilty; false while we have not
- * checked if the group came from one of its parents.
- */
- char guilty;
-
- /* true if the entry has been scanned for copies in the current parent
- */
- char scanned;
-
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
unsigned score;
};
+/*
+ * Any merge of blames happens on lists of blames that arrived via
+ * different parents in a single suspect. In this case, we want to
+ * sort according to the suspect line numbers as opposed to the final
+ * image line numbers. The function body is somewhat longish because
+ * it avoids unnecessary writes.
+ */
+
+static struct blame_entry *blame_merge(struct blame_entry *list1,
+ struct blame_entry *list2)
+{
+ struct blame_entry *p1 = list1, *p2 = list2,
+ **tail = &list1;
+
+ if (!p1)
+ return p2;
+ if (!p2)
+ return p1;
+
+ if (p1->s_lno <= p2->s_lno) {
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
+ for (;;) {
+ *tail = p2;
+ do {
+ tail = &p2->next;
+ if ((p2 = *tail) == NULL) {
+ *tail = p1;
+ return list1;
+ }
+ } while (p1->s_lno > p2->s_lno);
+ *tail = p1;
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
+}
+
+static void *get_next_blame(const void *p)
+{
+ return ((struct blame_entry *)p)->next;
+}
+
+static void set_next_blame(void *p1, void *p2)
+{
+ ((struct blame_entry *)p1)->next = p2;
+}
+
+/*
+ * Final image line numbers are all different, so we don't need a
+ * three-way comparison here.
+ */
+
+static int compare_blame_final(const void *p1, const void *p2)
+{
+ return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
+ ? 1 : -1;
+}
+
+static int compare_blame_suspect(const void *p1, const void *p2)
+{
+ const struct blame_entry *s1 = p1, *s2 = p2;
+ /*
+ * to allow for collating suspects, we sort according to the
+ * respective pointer value as the primary sorting criterion.
+ * The actual relation is pretty unimportant as long as it
+ * establishes a total order. Comparing as integers gives us
+ * that.
+ */
+ if (s1->suspect != s2->suspect)
+ return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1;
+ if (s1->s_lno == s2->s_lno)
+ return 0;
+ return s1->s_lno > s2->s_lno ? 1 : -1;
+}
+
+static struct blame_entry *blame_sort(struct blame_entry *head,
+ int (*compare_fn)(const void *, const void *))
+{
+ return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn);
+}
+
+static int compare_commits_by_reverse_commit_date(const void *a,
+ const void *b,
+ void *c)
+{
+ return -compare_commits_by_commit_date(a, b, c);
+}
+
/*
* The current state of the blame assignment.
*/
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
+ /* Priority queue for commits with unassigned blame records */
+ struct prio_queue commits;
struct rev_info *revs;
const char *path;
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
if (ent->suspect == next->suspect &&
- ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno) {
ent->num_lines += next->num_lines;
ent->next = next->next;
sanity_check_refcnt(sb);
}
+/*
+ * Merge the given sorted list of blames into a preexisting origin.
+ * If there were no previous blames to that commit, it is entered into
+ * the commit priority queue of the score board.
+ */
+
+static void queue_blames(struct scoreboard *sb, struct origin *porigin,
+ struct blame_entry *sorted)
+{
+ if (porigin->suspects)
+ porigin->suspects = blame_merge(porigin->suspects, sorted);
+ else {
+ struct origin *o;
+ for (o = porigin->commit->util; o; o = o->next) {
+ if (o->suspects) {
+ porigin->suspects = sorted;
+ return;
+ }
+ }
+ porigin->suspects = sorted;
+ prio_queue_put(&sb->commits, porigin->commit);
+ }
+}
+
/*
* Given a commit and a path in it, create a new origin structure.
* The callers that add blame to the scoreboard should use
o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
o->commit = commit;
o->refcnt = 1;
+ o->next = commit->util;
+ commit->util = o;
strcpy(o->path, path);
return o;
}
/*
* Locate an existing origin or create a new one.
+ * This moves the origin to front position in the commit util list.
*/
static struct origin *get_origin(struct scoreboard *sb,
struct commit *commit,
const char *path)
{
- struct blame_entry *e;
+ struct origin *o, *l;
- for (e = sb->ent; e; e = e->next) {
- if (e->suspect->commit == commit &&
- !strcmp(e->suspect->path, path))
- return origin_incref(e->suspect);
+ for (o = commit->util, l = NULL; o; l = o, o = o->next) {
+ if (!strcmp(o->path, path)) {
+ /* bump to front */
+ if (l) {
+ l->next = o->next;
+ o->next = commit->util;
+ commit->util = o;
+ }
+ return origin_incref(o);
+ }
}
return make_origin(commit, path);
}
struct commit *parent,
struct origin *origin)
{
- struct origin *porigin = NULL;
+ struct origin *porigin;
struct diff_options diff_opts;
const char *paths[2];
- if (parent->util) {
- /*
- * Each commit object can cache one origin in that
- * commit. This is a freestanding copy of origin and
- * not refcounted.
- */
- struct origin *cached = parent->util;
- if (!strcmp(cached->path, origin->path)) {
+ /* First check any existing origins */
+ for (porigin = parent->util; porigin; porigin = porigin->next)
+ if (!strcmp(porigin->path, origin->path)) {
/*
* The same path between origin and its parent
* without renaming -- the most common case.
*/
- porigin = get_origin(sb, parent, cached->path);
-
- /*
- * If the origin was newly created (i.e. get_origin
- * would call make_origin if none is found in the
- * scoreboard), it does not know the blob_sha1/mode,
- * so copy it. Otherwise porigin was in the
- * scoreboard and already knows blob_sha1/mode.
- */
- if (porigin->refcnt == 1) {
- hashcpy(porigin->blob_sha1, cached->blob_sha1);
- porigin->mode = cached->mode;
- }
- return porigin;
+ return origin_incref (porigin);
}
- /* otherwise it was not very useful; free it */
- free(parent->util);
- parent->util = NULL;
- }
/* See if the origin->path is different between parent
* and origin first. Most of the time they are the
}
diff_flush(&diff_opts);
free_pathspec(&diff_opts.pathspec);
- if (porigin) {
- /*
- * Create a freestanding copy that is not part of
- * the refcounted origin found in the scoreboard, and
- * cache it in the commit.
- */
- struct origin *cached;
-
- cached = make_origin(porigin->commit, porigin->path);
- hashcpy(cached->blob_sha1, porigin->blob_sha1);
- cached->mode = porigin->mode;
- parent->util = cached;
- }
return porigin;
}
}
/*
- * Link in a new blame entry to the scoreboard. Entries that cover the
- * same line range have been removed from the scoreboard previously.
+ * Append a new blame entry to a given output queue.
*/
-static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e)
{
- struct blame_entry *ent, *prev = NULL;
-
origin_incref(e->suspect);
- for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
- prev = ent;
-
- /* prev, if not NULL, is the last one that is below e */
-
- if (prev) {
- e->next = prev->next;
- prev->next = e;
- }
- else {
- e->next = sb->ent;
- sb->ent = e;
- }
+ e->next = **queue;
+ **queue = e;
+ *queue = &e->next;
}
/*
* src typically is on-stack; we want to copy the information in it to
- * a malloced blame_entry that is already on the linked list of the
- * scoreboard. The origin of dst loses a refcnt while the origin of src
- * gains one.
+ * a malloced blame_entry that gets added to the given queue. The
+ * origin of dst loses a refcnt.
*/
-static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+static void dup_entry(struct blame_entry ***queue,
+ struct blame_entry *dst, struct blame_entry *src)
{
- struct blame_entry *n;
-
- n = dst->next;
origin_incref(src->suspect);
origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
- dst->next = n;
- dst->score = 0;
+ dst->next = **queue;
+ **queue = dst;
+ *queue = &dst->next;
}
static const char *nth_line(struct scoreboard *sb, long lno)
/*
* split_overlap() divided an existing blame e into up to three parts
- * in split. Adjust the linked list of blames in the scoreboard to
+ * in split. Any assigned blame is moved to queue to
* reflect the split.
*/
-static void split_blame(struct scoreboard *sb,
+static void split_blame(struct blame_entry ***blamed,
+ struct blame_entry ***unblamed,
struct blame_entry *split,
struct blame_entry *e)
{
if (split[0].suspect && split[2].suspect) {
/* The first part (reuse storage for the existing entry e) */
- dup_entry(e, &split[0]);
+ dup_entry(unblamed, e, &split[0]);
/* The last part -- me */
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(unblamed, new_entry);
/* ... and the middle part -- parent */
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(blamed, new_entry);
}
else if (!split[0].suspect && !split[2].suspect)
/*
* The parent covers the entire area; reuse storage for
* e and replace it with the parent.
*/
- dup_entry(e, &split[1]);
+ dup_entry(blamed, e, &split[1]);
else if (split[0].suspect) {
/* me and then parent */
- dup_entry(e, &split[0]);
+ dup_entry(unblamed, e, &split[0]);
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(blamed, new_entry);
}
else {
/* parent and then me */
- dup_entry(e, &split[1]);
+ dup_entry(blamed, e, &split[1]);
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
- }
-
- if (DEBUG) { /* sanity */
- struct blame_entry *ent;
- int lno = sb->ent->lno, corrupt = 0;
-
- for (ent = sb->ent; ent; ent = ent->next) {
- if (lno != ent->lno)
- corrupt = 1;
- if (ent->s_lno < 0)
- corrupt = 1;
- lno += ent->num_lines;
- }
- if (corrupt) {
- lno = sb->ent->lno;
- for (ent = sb->ent; ent; ent = ent->next) {
- printf("L %8d l %8d n %8d\n",
- lno, ent->lno, ent->num_lines);
- lno = ent->lno + ent->num_lines;
- }
- die("oops");
- }
+ add_blame_entry(unblamed, new_entry);
}
}
}
/*
- * Helper for blame_chunk(). blame_entry e is known to overlap with
- * the patch hunk; split it and pass blame to the parent.
+ * reverse_blame reverses the list given in head, appending tail.
+ * That allows us to build lists in reverse order, then reverse them
+ * afterwards. This can be faster than building the list in proper
+ * order right away. The reason is that building in proper order
+ * requires writing a link in the _previous_ element, while building
+ * in reverse order just requires placing the list head into the
+ * _current_ element.
*/
-static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
- int tlno, int plno, int same,
- struct origin *parent)
-{
- struct blame_entry split[3];
-
- split_overlap(split, e, tlno, plno, same, parent);
- if (split[1].suspect)
- split_blame(sb, split, e);
- decref_split(split);
-}
-/*
- * Find the line number of the last line the target is suspected for.
- */
-static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+static struct blame_entry *reverse_blame(struct blame_entry *head,
+ struct blame_entry *tail)
{
- struct blame_entry *e;
- int last_in_target = -1;
-
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || e->suspect != target)
- continue;
- if (last_in_target < e->s_lno + e->num_lines)
- last_in_target = e->s_lno + e->num_lines;
+ while (head) {
+ struct blame_entry *next = head->next;
+ head->next = tail;
+ tail = head;
+ head = next;
}
- return last_in_target;
+ return tail;
}
/*
* Process one hunk from the patch between the current suspect for
- * blame_entry e and its parent. Find and split the overlap, and
- * pass blame to the overlapping part to the parent.
+ * blame_entry e and its parent. This first blames any unfinished
+ * entries before the chunk (which is where target and parent start
+ * differing) on the parent, and then splits blame entries at the
+ * start and at the end of the difference region. Since use of -M and
+ * -C options may lead to overlapping/duplicate source line number
+ * ranges, all we can rely on from sorting/merging is the order of the
+ * first suspect line number.
*/
-static void blame_chunk(struct scoreboard *sb,
- int tlno, int plno, int same,
- struct origin *target, struct origin *parent)
+static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
+ int tlno, int offset, int same,
+ struct origin *parent)
{
- struct blame_entry *e;
+ struct blame_entry *e = **srcq;
+ struct blame_entry *samep = NULL, *diffp = NULL;
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || e->suspect != target)
- continue;
- if (same <= e->s_lno)
- continue;
- if (tlno < e->s_lno + e->num_lines)
- blame_overlap(sb, e, tlno, plno, same, parent);
+ while (e && e->s_lno < tlno) {
+ struct blame_entry *next = e->next;
+ /*
+ * current record starts before differing portion. If
+ * it reaches into it, we need to split it up and
+ * examine the second part separately.
+ */
+ if (e->s_lno + e->num_lines > tlno) {
+ /* Move second half to a new record */
+ int len = tlno - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = e->suspect;
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to diffp */
+ n->next = diffp;
+ diffp = n;
+ } else
+ origin_decref(e->suspect);
+ /* Pass blame for everything before the differing
+ * chunk to the parent */
+ e->suspect = origin_incref(parent);
+ e->s_lno += offset;
+ e->next = samep;
+ samep = e;
+ e = next;
+ }
+ /*
+ * As we don't know how much of a common stretch after this
+ * diff will occur, the currently blamed parts are all that we
+ * can assign to the parent for now.
+ */
+
+ if (samep) {
+ **dstq = reverse_blame(samep, **dstq);
+ *dstq = &samep->next;
+ }
+ /*
+ * Prepend the split off portions: everything after e starts
+ * after the blameable portion.
+ */
+ e = reverse_blame(diffp, e);
+
+ /*
+ * Now retain records on the target while parts are different
+ * from the parent.
+ */
+ samep = NULL;
+ diffp = NULL;
+ while (e && e->s_lno < same) {
+ struct blame_entry *next = e->next;
+
+ /*
+ * If current record extends into sameness, need to split.
+ */
+ if (e->s_lno + e->num_lines > same) {
+ /*
+ * Move second half to a new record to be
+ * processed by later chunks
+ */
+ int len = same - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = origin_incref(e->suspect);
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to samep */
+ n->next = samep;
+ samep = n;
+ }
+ e->next = diffp;
+ diffp = e;
+ e = next;
}
+ **srcq = reverse_blame(diffp, reverse_blame(samep, e));
+ /* Move across elements that are in the unblamable portion */
+ if (diffp)
+ *srcq = &diffp->next;
}
struct blame_chunk_cb_data {
- struct scoreboard *sb;
- struct origin *target;
struct origin *parent;
- long plno;
- long tlno;
+ long offset;
+ struct blame_entry **dstq;
+ struct blame_entry **srcq;
};
+/* diff chunks are from parent to target */
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, start_b, d->target, d->parent);
- d->plno = start_a + count_a;
- d->tlno = start_b + count_b;
+ if (start_a - start_b != d->offset)
+ die("internal error in blame::blame_chunk_cb");
+ blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
+ start_b + count_b, d->parent);
+ d->offset = start_a + count_a - (start_b + count_b);
return 0;
}
* for the lines it is suspected to its parent. Run diff to find
* which lines came from parent and pass blame for them.
*/
-static int pass_blame_to_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
+static void pass_blame_to_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
{
- int last_in_target;
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
+ struct blame_entry *newdest = NULL;
- memset(&d, 0, sizeof(d));
- d.sb = sb; d.target = target; d.parent = parent;
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
+ if (!target->suspects)
+ return; /* nothing remains for this target */
+
+ d.parent = parent;
+ d.offset = 0;
+ d.dstq = &newdest; d.srcq = &target->suspects;
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
num_get_patch++;
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);
+ /* The rest are the same as the parent */
+ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
+ *d.dstq = NULL;
+ queue_blames(sb, parent, newdest);
- return 0;
+ return;
}
/*
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
+/* Move all blame entries from list *source that have a score smaller
+ * than score_min to the front of list *small.
+ * Returns a pointer to the link pointing to the old head of the small list.
+ */
+
+static struct blame_entry **filter_small(struct scoreboard *sb,
+ struct blame_entry **small,
+ struct blame_entry **source,
+ unsigned score_min)
+{
+ struct blame_entry *p = *source;
+ struct blame_entry *oldsmall = *small;
+ while (p) {
+ if (ent_score(sb, p) <= score_min) {
+ *small = p;
+ small = &p->next;
+ p = *small;
+ } else {
+ *source = p;
+ source = &p->next;
+ p = *source;
+ }
+ }
+ *small = oldsmall;
+ *source = NULL;
+ return small;
+}
+
/*
* See if lines currently target is suspected for can be attributed to
* parent.
*/
-static int find_move_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
+static void find_move_in_parent(struct scoreboard *sb,
+ struct blame_entry ***blamed,
+ struct blame_entry **toosmall,
+ struct origin *target,
+ struct origin *parent)
{
- int last_in_target, made_progress;
struct blame_entry *e, split[3];
+ struct blame_entry *unblamed = target->suspects;
+ struct blame_entry *leftover = NULL;
mmfile_t file_p;
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
+ if (!unblamed)
+ return; /* nothing remains for this target */
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
if (!file_p.ptr)
- return 0;
+ return;
- made_progress = 1;
- while (made_progress) {
- made_progress = 0;
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || e->suspect != target ||
- ent_score(sb, e) < blame_move_score)
- continue;
+ /* At each iteration, unblamed has a NULL-terminated list of
+ * entries that have not yet been tested for blame. leftover
+ * contains the reversed list of entries that have been tested
+ * without being assignable to the parent.
+ */
+ do {
+ struct blame_entry **unblamedtail = &unblamed;
+ struct blame_entry *next;
+ for (e = unblamed; e; e = next) {
+ next = e->next;
find_copy_in_blob(sb, e, parent, split, &file_p);
if (split[1].suspect &&
blame_move_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, e);
- made_progress = 1;
+ split_blame(blamed, &unblamedtail, split, e);
+ } else {
+ e->next = leftover;
+ leftover = e;
}
decref_split(split);
}
- }
- return 0;
+ *unblamedtail = NULL;
+ toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score);
+ } while (unblamed);
+ target->suspects = reverse_blame(leftover, NULL);
}
struct blame_list {
* Count the number of entries the target is suspected for,
* and prepare a list of entry and the best split.
*/
-static struct blame_list *setup_blame_list(struct scoreboard *sb,
- struct origin *target,
- int min_score,
+static struct blame_list *setup_blame_list(struct blame_entry *unblamed,
int *num_ents_p)
{
struct blame_entry *e;
int num_ents, i;
struct blame_list *blame_list = NULL;
- for (e = sb->ent, num_ents = 0; e; e = e->next)
- if (!e->scanned && !e->guilty &&
- e->suspect == target &&
- min_score < ent_score(sb, e))
- num_ents++;
+ for (e = unblamed, num_ents = 0; e; e = e->next)
+ num_ents++;
if (num_ents) {
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
- for (e = sb->ent, i = 0; e; e = e->next)
- if (!e->scanned && !e->guilty &&
- e->suspect == target &&
- min_score < ent_score(sb, e))
- blame_list[i++].ent = e;
+ for (e = unblamed, i = 0; e; e = e->next)
+ blame_list[i++].ent = e;
}
*num_ents_p = num_ents;
return blame_list;
}
-/*
- * Reset the scanned status on all entries.
- */
-static void reset_scanned_flag(struct scoreboard *sb)
-{
- struct blame_entry *e;
- for (e = sb->ent; e; e = e->next)
- e->scanned = 0;
-}
-
/*
* For lines target is suspected for, see if we can find code movement
* across file boundary from the parent commit. porigin is the path
* in the parent we already tried.
*/
-static int find_copy_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct commit *parent,
- struct origin *porigin,
- int opt)
+static void find_copy_in_parent(struct scoreboard *sb,
+ struct blame_entry ***blamed,
+ struct blame_entry **toosmall,
+ struct origin *target,
+ struct commit *parent,
+ struct origin *porigin,
+ int opt)
{
struct diff_options diff_opts;
int i, j;
- int retval;
struct blame_list *blame_list;
int num_ents;
+ struct blame_entry *unblamed = target->suspects;
+ struct blame_entry *leftover = NULL;
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list)
- return 1; /* nothing remains for this target */
+ if (!unblamed)
+ return; /* nothing remains for this target */
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
diffcore_std(&diff_opts);
- retval = 0;
- while (1) {
- int made_progress = 0;
+ do {
+ struct blame_entry **unblamedtail = &unblamed;
+ blame_list = setup_blame_list(unblamed, &num_ents);
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
struct blame_entry *split = blame_list[j].split;
if (split[1].suspect &&
blame_copy_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, blame_list[j].ent);
- made_progress = 1;
+ split_blame(blamed, &unblamedtail, split,
+ blame_list[j].ent);
+ } else {
+ blame_list[j].ent->next = leftover;
+ leftover = blame_list[j].ent;
}
- else
- blame_list[j].ent->scanned = 1;
decref_split(split);
}
free(blame_list);
-
- if (!made_progress)
- break;
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list) {
- retval = 1;
- break;
- }
- }
- reset_scanned_flag(sb);
+ *unblamedtail = NULL;
+ toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score);
+ } while (unblamed);
+ target->suspects = reverse_blame(leftover, NULL);
diff_flush(&diff_opts);
free_pathspec(&diff_opts.pathspec);
- return retval;
}
/*
static void pass_whole_blame(struct scoreboard *sb,
struct origin *origin, struct origin *porigin)
{
- struct blame_entry *e;
+ struct blame_entry *e, *suspects;
if (!porigin->file.ptr && origin->file.ptr) {
/* Steal its file */
porigin->file = origin->file;
origin->file.ptr = NULL;
}
- for (e = sb->ent; e; e = e->next) {
- if (e->suspect != origin)
- continue;
+ suspects = origin->suspects;
+ origin->suspects = NULL;
+ for (e = suspects; e; e = e->next) {
origin_incref(porigin);
origin_decref(e->suspect);
e->suspect = porigin;
}
+ queue_blames(sb, porigin, suspects);
}
/*
return cnt;
}
+/* Distribute collected unsorted blames to the respected sorted lists
+ * in the various origins.
+ */
+static void distribute_blame(struct scoreboard *sb, struct blame_entry *blamed)
+{
+ blamed = blame_sort(blamed, compare_blame_suspect);
+ while (blamed)
+ {
+ struct origin *porigin = blamed->suspect;
+ struct blame_entry *suspects = NULL;
+ do {
+ struct blame_entry *next = blamed->next;
+ blamed->next = suspects;
+ suspects = blamed;
+ blamed = next;
+ } while (blamed && blamed->suspect == porigin);
+ suspects = reverse_blame(suspects, NULL);
+ queue_blames(sb, porigin, suspects);
+ }
+}
+
#define MAXSG 16
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
struct commit_list *sg;
struct origin *sg_buf[MAXSG];
struct origin *porigin, **sg_origin = sg_buf;
+ struct blame_entry *toosmall = NULL;
+ struct blame_entry *blames, **blametail = &blames;
num_sg = num_scapegoats(revs, commit);
if (!num_sg)
origin_incref(porigin);
origin->previous = porigin;
}
- if (pass_blame_to_parent(sb, origin, porigin))
+ pass_blame_to_parent(sb, origin, porigin);
+ if (!origin->suspects)
goto finish;
}
/*
* Optionally find moves in parents' files.
*/
- if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct origin *porigin = sg_origin[i];
- if (!porigin)
- continue;
- if (find_move_in_parent(sb, origin, porigin))
- goto finish;
+ if (opt & PICKAXE_BLAME_MOVE) {
+ filter_small(sb, &toosmall, &origin->suspects, blame_move_score);
+ if (origin->suspects) {
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ find_move_in_parent(sb, &blametail, &toosmall, origin, porigin);
+ if (!origin->suspects)
+ break;
+ }
}
+ }
/*
* Optionally find copies from parents' files.
*/
- if (opt & PICKAXE_BLAME_COPY)
+ if (opt & PICKAXE_BLAME_COPY) {
+ if (blame_copy_score > blame_move_score)
+ filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
+ else if (blame_copy_score < blame_move_score) {
+ origin->suspects = blame_merge(origin->suspects, toosmall);
+ toosmall = NULL;
+ filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
+ }
+ if (!origin->suspects)
+ goto finish;
+
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
- if (find_copy_in_parent(sb, origin, sg->item,
- porigin, opt))
+ find_copy_in_parent(sb, &blametail, &toosmall,
+ origin, sg->item, porigin, opt);
+ if (!origin->suspects)
goto finish;
}
+ }
- finish:
+finish:
+ *blametail = NULL;
+ distribute_blame(sb, blames);
+ /*
+ * prepend toosmall to origin->suspects
+ *
+ * There is no point in sorting: this ends up on a big
+ * unsorted list in the caller anyway.
+ */
+ if (toosmall) {
+ struct blame_entry **tail = &toosmall;
+ while (*tail)
+ tail = &(*tail)->next;
+ *tail = origin->suspects;
+ origin->suspects = toosmall;
+ }
for (i = 0; i < num_sg; i++) {
if (sg_origin[i]) {
drop_origin_blob(sg_origin[i]);
}
/*
- * The blame_entry is found to be guilty for the range. Mark it
- * as such, and show it in incremental output.
+ * The blame_entry is found to be guilty for the range.
+ * Show it in incremental output.
*/
static void found_guilty_entry(struct blame_entry *ent)
{
- if (ent->guilty)
- return;
- ent->guilty = 1;
if (incremental) {
struct origin *suspect = ent->suspect;
}
/*
- * The main loop -- while the scoreboard has lines whose true origin
- * is still unknown, pick one blame_entry, and allow its current
- * suspect to pass blames to its parents.
- */
+ * The main loop -- while we have blobs with lines whose true origin
+ * is still unknown, pick one blob, and allow its lines to pass blames
+ * to its parents. */
static void assign_blame(struct scoreboard *sb, int opt)
{
struct rev_info *revs = sb->revs;
+ struct commit *commit = prio_queue_get(&sb->commits);
- while (1) {
+ while (commit) {
struct blame_entry *ent;
- struct commit *commit;
- struct origin *suspect = NULL;
+ struct origin *suspect = commit->util;
/* find one suspect to break down */
- for (ent = sb->ent; !suspect && ent; ent = ent->next)
- if (!ent->guilty)
- suspect = ent->suspect;
- if (!suspect)
- return; /* all done */
+ while (suspect && !suspect->suspects)
+ suspect = suspect->next;
+
+ if (!suspect) {
+ commit = prio_queue_get(&sb->commits);
+ continue;
+ }
+
+ assert(commit == suspect->commit);
/*
* We will use this suspect later in the loop,
* so hold onto it in the meantime.
*/
origin_incref(suspect);
- commit = suspect->commit;
parse_commit(commit);
if (reverse ||
(!(commit->object.flags & UNINTERESTING) &&
commit->object.flags |= UNINTERESTING;
/* Take responsibility for the remaining entries */
- for (ent = sb->ent; ent; ent = ent->next)
- if (ent->suspect == suspect)
+ ent = suspect->suspects;
+ if (ent) {
+ suspect->guilty = 1;
+ for (;;) {
+ struct blame_entry *next = ent->next;
found_guilty_entry(ent);
+ if (next) {
+ ent = next;
+ continue;
+ }
+ ent->next = sb->ent;
+ sb->ent = suspect->suspects;
+ suspect->suspects = NULL;
+ break;
+ }
+ }
origin_decref(suspect);
if (DEBUG) /* sanity */
static const char *format_time(unsigned long time, const char *tz_str,
int show_raw_time)
{
- static char time_buf[128];
+ static struct strbuf time_buf = STRBUF_INIT;
+ strbuf_reset(&time_buf);
if (show_raw_time) {
- snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
+ strbuf_addf(&time_buf, "%lu %s", time, tz_str);
}
else {
const char *time_str;
- int time_len;
+ size_t time_width;
int tz;
tz = atoi(tz_str);
time_str = show_date(time, tz, blame_date_mode);
- time_len = strlen(time_str);
- memcpy(time_buf, time_str, time_len);
- memset(time_buf + time_len, ' ', blame_date_width - time_len);
+ strbuf_addstr(&time_buf, time_str);
+ /*
+ * Add space paddings to time_buf to display a fixed width
+ * string, and use time_width for display width calibration.
+ */
+ for (time_width = utf8_strwidth(time_str);
+ time_width < blame_date_width;
+ time_width++)
+ strbuf_addch(&time_buf, ' ');
}
- return time_buf;
+ return time_buf.buf;
}
#define OUTPUT_ANNOTATE_COMPAT 001
char hex[41];
strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
- printf("%s%c%d %d %d\n",
+ printf("%s %d %d %d\n",
hex,
- ent->guilty ? ' ' : '*', /* purely for debugging */
ent->s_lno + 1,
ent->lno + 1,
ent->num_lines);
if (option & OUTPUT_PORCELAIN) {
for (ent = sb->ent; ent; ent = ent->next) {
- struct blame_entry *oth;
- struct origin *suspect = ent->suspect;
- struct commit *commit = suspect->commit;
+ int count = 0;
+ struct origin *suspect;
+ struct commit *commit = ent->suspect->commit;
if (commit->object.flags & MORE_THAN_ONE_PATH)
continue;
- for (oth = ent->next; oth; oth = oth->next) {
- if ((oth->suspect->commit != commit) ||
- !strcmp(oth->suspect->path, suspect->path))
- continue;
- commit->object.flags |= MORE_THAN_ONE_PATH;
- break;
+ for (suspect = commit->util; suspect; suspect = suspect->next) {
+ if (suspect->guilty && count++) {
+ commit->object.flags |= MORE_THAN_ONE_PATH;
+ break;
+ }
}
}
}
if (strbuf_read(&buf, 0, 0) < 0)
die_errno("failed to read from stdin");
}
- convert_to_git(path, buf.buf, buf.len, &buf, 0);
origin->file.ptr = buf.buf;
origin->file.size = buf.len;
pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
- commit->util = origin;
/*
* Read the current index, replace the path entry with
blame_date_width = sizeof("2006-10-19");
break;
case DATE_RELATIVE:
- /* "normal" is used as the fallback for "relative" */
+ /* TRANSLATORS: This string is used to tell us the maximum
+ display width for a relative timestamp in "git blame"
+ output. For C locale, "4 years, 11 months ago", which
+ takes 22 places, is the longest among various forms of
+ relative timestamps, but your language may need more or
+ fewer display columns. */
+ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
+ break;
case DATE_LOCAL:
case DATE_NORMAL:
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
memset(&sb, 0, sizeof(sb));
sb.revs = &revs;
- if (!reverse)
+ if (!reverse) {
final_commit_name = prepare_final(&sb);
+ sb.commits.compare = compare_commits_by_commit_date;
+ }
else if (contents_from)
die("--contents and --children do not blend well.");
- else
+ else {
final_commit_name = prepare_initial(&sb);
+ sb.commits.compare = compare_commits_by_reverse_commit_date;
+ }
if (!sb.final) {
/*
ent->next = next;
origin_incref(o);
}
+
+ o->suspects = ent;
+ prio_queue_put(&sb.commits, o->commit);
+
origin_decref(o);
range_set_release(&ranges);
string_list_clear(&range_list, 0);
- sb.ent = ent;
+ sb.ent = NULL;
sb.path = path;
read_mailmap(&mailmap, NULL);
if (incremental)
return 0;
+ sb.ent = blame_sort(sb.ent, compare_blame_final);
+
coalesce(&sb);
if (!(output_option & OUTPUT_PORCELAIN))
/* Nothing to do. */
} else if (opts->force_detach || !new->path) { /* No longer on any branch. */
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
- REF_NODEREF, DIE_ON_ERR);
+ REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old->path && advice_detached_head)
detach_advice(new->name);
}
}
if (old->path && old->name) {
- char log_file[PATH_MAX], ref_file[PATH_MAX];
-
- git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
- git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
- if (!file_exists(ref_file) && file_exists(log_file))
- remove_path(log_file);
+ if (!ref_exists(old->path) && reflog_exists(old->path))
+ delete_reflog(old->path);
}
}
remove_branch_state();
if (!has_sha1_file(ref->old_sha1))
continue;
update_ref(msg, ref->name, ref->old_sha1,
- NULL, 0, DIE_ON_ERR);
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
}
}
create_symref("HEAD", our->name, NULL);
if (!option_bare) {
const char *head = skip_prefix(our->name, "refs/heads/");
- update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+ update_ref(msg, "HEAD", our->old_sha1, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
install_branch_config(0, head, option_origin, our->name);
}
} else if (our) {
struct commit *c = lookup_commit_reference(our->old_sha1);
/* --branch specifies a non-branch (i.e. tags), detach HEAD */
update_ref(msg, "HEAD", c->object.sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
+ NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
} else if (remote) {
/*
* We know remote HEAD points to a non-branch, or
* Detach HEAD in all these cases.
*/
update_ref(msg, "HEAD", remote->old_sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
+ NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
}
}
return 1;
}
+static int parse_force_date(const char *in, char *out, int len)
+{
+ if (len < 1)
+ return -1;
+ *out++ = '@';
+ len--;
+
+ if (parse_date(in, out, len) < 0) {
+ int errors = 0;
+ unsigned long t = approxidate_careful(in, &errors);
+ if (errors)
+ return -1;
+ snprintf(out, len, "%lu", t);
+ }
+
+ return 0;
+}
+
static void determine_author_info(struct strbuf *author_ident)
{
char *name, *email, *date;
struct ident_split author;
+ char date_buf[64];
name = getenv("GIT_AUTHOR_NAME");
email = getenv("GIT_AUTHOR_EMAIL");
email = xstrndup(lb + 2, rb - (lb + 2));
}
- if (force_date)
- date = force_date;
+ if (force_date) {
+ if (parse_force_date(force_date, date_buf, sizeof(date_buf)))
+ die(_("invalid date format: %s"), force_date);
+ date = date_buf;
+ }
+
strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
if (!split_ident_line(&author, author_ident->buf, author_ident->len) &&
sane_ident_split(&author)) {
}
}
-static char *cut_ident_timestamp_part(char *string)
+static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf)
+{
+ if (split_ident_line(id, buf->buf, buf->len) ||
+ !sane_ident_split(id))
+ die(_("Malformed ident string: '%s'"), buf->buf);
+}
+
+static int author_date_is_interesting(void)
+{
+ return author_message || force_date;
+}
+
+static void adjust_comment_line_char(const struct strbuf *sb)
{
- char *ket = strrchr(string, '>');
- if (!ket || ket[1] != ' ')
- die(_("Malformed ident string: '%s'"), string);
- *++ket = '\0';
- return ket;
+ char candidates[] = "#;@!$%^&|:";
+ char *candidate;
+ const char *p;
+
+ comment_line_char = candidates[0];
+ if (!memchr(sb->buf, comment_line_char, sb->len))
+ return;
+
+ p = sb->buf;
+ candidate = strchr(candidates, *p);
+ if (candidate)
+ *candidate = ' ';
+ for (p = sb->buf; *p; p++) {
+ if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
+ candidate = strchr(candidates, p[1]);
+ if (candidate)
+ *candidate = ' ';
+ }
+ }
+
+ for (p = candidates; *p == ' '; p++)
+ ;
+ if (!*p)
+ die(_("unable to select a comment character that is not used\n"
+ "in the current commit message"));
+ comment_line_char = *p;
}
static int prepare_to_commit(const char *index_file, const char *prefix,
} else if (use_message) {
char *buffer;
buffer = strstr(use_message_buffer, "\n\n");
- if (!use_editor && (!buffer || buffer[2] == '\0'))
- die(_("commit has empty message"));
- strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ if (buffer)
+ strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
} else if (fixup_message) {
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
die_errno(_("could not write commit template"));
+ if (auto_comment_line_char)
+ adjust_comment_line_char(&sb);
strbuf_release(&sb);
/* This checks if committer ident is explicitly given */
if (use_editor && include_status) {
int ident_shown = 0;
int saved_color_setting;
- char *ai_tmp, *ci_tmp;
+ struct ident_split ci, ai;
+
if (whence != FROM_COMMIT) {
if (cleanup_mode == CLEANUP_SCISSORS)
wt_status_add_cut_line(s->fp);
status_printf_ln(s, GIT_COLOR_NORMAL,
"%s", only_include_assumed);
- ai_tmp = cut_ident_timestamp_part(author_ident->buf);
- ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
- if (strcmp(author_ident->buf, committer_ident.buf))
+ split_ident_or_die(&ai, author_ident);
+ split_ident_or_die(&ci, &committer_ident);
+
+ if (ident_cmp(&ai, &ci))
status_printf_ln(s, GIT_COLOR_NORMAL,
_("%s"
- "Author: %s"),
+ "Author: %.*s <%.*s>"),
ident_shown++ ? "" : "\n",
- author_ident->buf);
+ (int)(ai.name_end - ai.name_begin), ai.name_begin,
+ (int)(ai.mail_end - ai.mail_begin), ai.mail_begin);
+
+ if (author_date_is_interesting())
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ _("%s"
+ "Date: %s"),
+ ident_shown++ ? "" : "\n",
+ show_ident_date(&ai, DATE_NORMAL));
if (!committer_ident_sufficiently_given())
status_printf_ln(s, GIT_COLOR_NORMAL,
_("%s"
- "Committer: %s"),
+ "Committer: %.*s <%.*s>"),
ident_shown++ ? "" : "\n",
- committer_ident.buf);
+ (int)(ci.name_end - ci.name_begin), ci.name_begin,
+ (int)(ci.mail_end - ci.mail_begin), ci.mail_begin);
if (ident_shown)
- status_printf_ln(s, GIT_COLOR_NORMAL, "");
+ status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
saved_color_setting = s->use_color;
s->use_color = 0;
commitable = run_status(s->fp, index_file, prefix, 1, s);
s->use_color = saved_color_setting;
-
- *ai_tmp = ' ';
- *ci_tmp = ' ';
} else {
unsigned char sha1[20];
const char *parent = "HEAD";
strbuf_addstr(&format, "\n Author: ");
strbuf_addbuf_percentquote(&format, &author_ident);
}
+ if (author_date_is_interesting()) {
+ struct strbuf date = STRBUF_INIT;
+ format_commit_message(commit, "%ad", &date, &pctx);
+ strbuf_addstr(&format, "\n Date: ");
+ strbuf_addbuf_percentquote(&format, &date);
+ strbuf_release(&date);
+ }
if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
? NULL
: current_head->object.sha1,
0, NULL);
+ if (!ref_lock) {
+ rollback_index_files();
+ die(_("cannot lock HEAD ref"));
+ }
nl = strchr(sb.buf, '\n');
if (nl)
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
- if (!ref_lock) {
- rollback_index_files();
- die(_("cannot lock HEAD ref"));
- }
if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
rollback_index_files();
die(_("cannot update HEAD ref"));
argv[i] = path_list->items[i].string;
argv[path_list->nr] = NULL;
- if (prefix && chdir(prefix))
- die(_("Failed to chdir: %s"), prefix);
- status = run_command_v_opt(argv, RUN_USING_SHELL);
+ status = run_command_v_opt_cd_env(argv, RUN_USING_SHELL, prefix, NULL);
if (status)
exit(status);
free(argv);
if (len > 4 && is_dir_sep(pager[len - 5]))
pager += len - 4;
+ if (opt.ignore_case && !strcmp("less", pager))
+ string_list_append(&path_list, "-I");
+
if (!strcmp("less", pager) || !strcmp("vi", pager)) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "+/%s%s",
int ofs_first, ofs_last;
};
-#if !defined(NO_PTHREADS) && defined(NO_THREAD_SAFE_PREAD)
-/* pread() emulation 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;
+ int pack_fd;
};
/*
static unsigned deepest_delta;
static git_SHA_CTX input_ctx;
static uint32_t input_crc32;
-static int input_fd, output_fd, pack_fd;
+static int input_fd, output_fd;
+static const char *curr_pack;
#ifndef NO_PTHREADS
*/
static void init_thread(void)
{
+ int i;
init_recursive_mutex(&read_mutex);
pthread_mutex_init(&counter_mutex, NULL);
pthread_mutex_init(&work_mutex, NULL);
pthread_mutex_init(&deepest_delta_mutex, NULL);
pthread_key_create(&key, NULL);
thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ for (i = 0; i < nr_threads; i++) {
+ thread_data[i].pack_fd = open(curr_pack, O_RDONLY);
+ if (thread_data[i].pack_fd == -1)
+ die_errno(_("unable to open %s"), curr_pack);
+ }
+
threads_active = 1;
}
static void cleanup_thread(void)
{
+ int i;
if (!threads_active)
return;
threads_active = 0;
pthread_mutex_destroy(&work_mutex);
if (show_stat)
pthread_mutex_destroy(&deepest_delta_mutex);
+ for (i = 0; i < nr_threads; i++)
+ close(thread_data[i].pack_fd);
pthread_key_delete(key);
free(thread_data);
}
if (!(obj->flags & FLAG_CHECKED)) {
unsigned long size;
int type = sha1_object_info(obj->sha1, &size);
- if (type != obj->type || type <= 0)
- die(_("object of unexpected type"));
+ if (type <= 0)
+ die(_("did not receive expected object %s"),
+ sha1_to_hex(obj->sha1));
+ if (type != obj->type)
+ die(_("object %s: expected type %s, found %s"),
+ sha1_to_hex(obj->sha1),
+ typename(obj->type), typename(type));
obj->flags |= FLAG_CHECKED;
return 1;
}
output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd < 0)
die_errno(_("unable to create '%s'"), pack_name);
- pack_fd = output_fd;
+ nothread_data.pack_fd = output_fd;
} else {
input_fd = open(pack_name, O_RDONLY);
if (input_fd < 0)
die_errno(_("cannot open packfile '%s'"), pack_name);
output_fd = -1;
- pack_fd = input_fd;
+ nothread_data.pack_fd = input_fd;
}
git_SHA1_Init(&input_ctx);
return pack_name;
do {
ssize_t n = (len < 64*1024) ? len : 64*1024;
- n = pread(pack_fd, inbuf, n, from);
+ n = xpread(get_thread_data()->pack_fd, inbuf, n, from);
if (n < 0)
die_errno(_("cannot pread pack file"));
if (!n)
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
- const char *curr_pack, *curr_index;
+ const char *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
char *index_name_buf = NULL, *keep_name_buf = NULL;
if (rev->show_notes)
init_display_notes(&rev->notes_opt);
- if (rev->diffopt.pickaxe || rev->diffopt.filter)
+ if (rev->diffopt.pickaxe || rev->diffopt.filter ||
+ DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES))
rev->always_show_header = 0;
- if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
- rev->always_show_header = 0;
- if (rev->diffopt.pathspec.nr != 1)
- usage("git logs can only follow renames on one pathname at a time");
- }
if (source)
rev->show_source = 1;
}
if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
for (i = 0; header[i]; i++) {
- if (!memcmp("Subject", header[i], 7)) {
+ if (!strcmp("Subject", header[i])) {
handle_header(&hdr_data[i], line);
ret = 1;
goto check_header_out;
else
continue;
- if (!memcmp(header[i], "Subject", 7)) {
+ if (!strcmp(header[i], "Subject")) {
if (!keep_subject) {
cleanup_subject(hdr);
cleanup_space(hdr);
}
output_header_lines(fout, "Subject", hdr);
- } else if (!memcmp(header[i], "From", 4)) {
+ } else if (!strcmp(header[i], "From")) {
cleanup_space(hdr);
handle_from(hdr);
fprintf(fout, "Author: %s\n", name.buf);
static int allow_rerere_auto;
static int abort_current_merge;
static int show_progress = -1;
-static int default_to_upstream;
+static int default_to_upstream = 1;
static const char *sign_commit;
static struct strategy all_strategy[] = {
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD",
new_head, head, 0,
- DIE_ON_ERR);
+ UPDATE_REFS_DIE_ON_ERR);
/*
* We ignore errors in 'gc --auto', since the
* user should see them.
die(_("%s - not something we can merge"), argv[0]);
read_empty(remote_head->object.sha1, 0);
update_ref("initial pull", "HEAD", remote_head->object.sha1,
- NULL, 0, DIE_ON_ERR);
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
} else {
struct strbuf merge_names = STRBUF_INIT;
}
update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
- NULL, 0, DIE_ON_ERR);
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */
}
} else if (cache_name_pos(src, length) < 0)
bad = _("not under version control");
- else if (lstat(dst, &st) == 0) {
+ else if (lstat(dst, &st) == 0 &&
+ (!ignore_case || strcasecmp(src, dst))) {
bad = _("destination exists");
if (force) {
/*
strbuf_insert(&msg, 0, "notes: ", 7);
update_ref(msg.buf, o->local_ref, sha1,
is_null_sha1(parent_sha1) ? NULL : parent_sha1,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
free_notes(t);
strbuf_release(&msg);
if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
/* Update default notes ref with new commit */
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die("Failed to store link to current notes ref (%s)",
#include "builtin.h"
-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
+static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result)
{
- unsigned char result[20];
char name[50];
if (!patchlen)
return;
- git_SHA1_Final(result, c);
memcpy(name, sha1_to_hex(id), 41);
printf("%s %s\n", sha1_to_hex(result), name);
- git_SHA1_Init(c);
}
static int remove_space(char *line)
return 1;
}
-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf)
+static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx)
+{
+ unsigned char hash[20];
+ unsigned short carry = 0;
+ int i;
+
+ git_SHA1_Final(hash, ctx);
+ git_SHA1_Init(ctx);
+ /* 20-byte sum, with carry */
+ for (i = 0; i < 20; ++i) {
+ carry += result[i] + hash[i];
+ result[i] = carry;
+ carry >>= 8;
+ }
+}
+
+static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
+ struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
+ git_SHA_CTX ctx;
+
+ git_SHA1_Init(&ctx);
+ hashclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
char *line = line_buf->buf;
break;
/* Else we're parsing another header. */
+ if (stable)
+ flush_one_hunk(result, &ctx);
before = after = -1;
}
/* Compute the sha without whitespace */
len = remove_space(line);
patchlen += len;
- git_SHA1_Update(ctx, line, len);
+ git_SHA1_Update(&ctx, line, len);
}
if (!found_next)
hashclr(next_sha1);
+ flush_one_hunk(result, &ctx);
+
return patchlen;
}
-static void generate_id_list(void)
+static void generate_id_list(int stable)
{
- unsigned char sha1[20], n[20];
- git_SHA_CTX ctx;
+ unsigned char sha1[20], n[20], result[20];
int patchlen;
struct strbuf line_buf = STRBUF_INIT;
- git_SHA1_Init(&ctx);
hashclr(sha1);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, &ctx, &line_buf);
- flush_current_id(patchlen, sha1, &ctx);
+ patchlen = get_one_patchid(n, result, &line_buf, stable);
+ flush_current_id(patchlen, sha1, result);
hashcpy(sha1, n);
}
strbuf_release(&line_buf);
}
-static const char patch_id_usage[] = "git patch-id < patch";
+static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch";
+
+static int git_patch_id_config(const char *var, const char *value, void *cb)
+{
+ int *stable = cb;
+
+ if (!strcmp(var, "patchid.stable")) {
+ *stable = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
int cmd_patch_id(int argc, const char **argv, const char *prefix)
{
- if (argc != 1)
+ int stable = -1;
+
+ git_config(git_patch_id_config, &stable);
+
+ /* If nothing is set, default to unstable. */
+ if (stable < 0)
+ stable = 0;
+
+ if (argc == 2 && !strcmp(argv[1], "--stable"))
+ stable = 1;
+ else if (argc == 2 && !strcmp(argv[1], "--unstable"))
+ stable = 0;
+ else if (argc != 1)
usage(patch_id_usage);
- generate_id_list();
+ generate_id_list(stable);
return 0;
}
if (!lock)
return error("cannot lock ref '%s'", ref);
log_file = git_pathdup("logs/%s", ref);
- if (!file_exists(log_file))
+ if (!reflog_exists(ref))
goto finish;
if (!cmd->dry_run) {
newlog_path = git_pathdup("logs/%s.lock", ref);
#include "builtin.h"
#include "refs.h"
#include "parse-options.h"
+#include "run-command.h"
static const char * const git_replace_usage[] = {
N_("git replace [-f] <object> <replacement>"),
+ N_("git replace [-f] --edit <object>"),
N_("git replace -d <object>..."),
N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL
return 0;
}
-static int replace_object(const char *object_ref, const char *replace_ref,
- int force)
+static void check_ref_valid(unsigned char object[20],
+ unsigned char prev[20],
+ char *ref,
+ int ref_size,
+ int force)
{
- unsigned char object[20], prev[20], repl[20];
- enum object_type obj_type, repl_type;
- char ref[PATH_MAX];
- struct ref_lock *lock;
-
- if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
- if (get_sha1(replace_ref, repl))
- die("Failed to resolve '%s' as a valid ref.", replace_ref);
-
- if (snprintf(ref, sizeof(ref),
+ if (snprintf(ref, ref_size,
"refs/replace/%s",
- sha1_to_hex(object)) > sizeof(ref) - 1)
+ sha1_to_hex(object)) > ref_size - 1)
die("replace ref name too long: %.*s...", 50, ref);
if (check_refname_format(ref, 0))
die("'%s' is not a valid ref name.", ref);
+ if (read_ref(ref, prev))
+ hashclr(prev);
+ else if (!force)
+ die("replace ref '%s' already exists", ref);
+}
+
+static int replace_object_sha1(const char *object_ref,
+ unsigned char object[20],
+ const char *replace_ref,
+ unsigned char repl[20],
+ int force)
+{
+ unsigned char prev[20];
+ enum object_type obj_type, repl_type;
+ char ref[PATH_MAX];
+ struct ref_lock *lock;
+
obj_type = sha1_object_info(object, NULL);
repl_type = sha1_object_info(repl, NULL);
if (!force && obj_type != repl_type)
object_ref, typename(obj_type),
replace_ref, typename(repl_type));
- if (read_ref(ref, prev))
- hashclr(prev);
- else if (!force)
- die("replace ref '%s' already exists", ref);
+ check_ref_valid(object, prev, ref, sizeof(ref), force);
lock = lock_any_ref_for_update(ref, prev, 0, NULL);
if (!lock)
return 0;
}
+static int replace_object(const char *object_ref, const char *replace_ref, int force)
+{
+ unsigned char object[20], repl[20];
+
+ if (get_sha1(object_ref, object))
+ die("Failed to resolve '%s' as a valid ref.", object_ref);
+ if (get_sha1(replace_ref, repl))
+ die("Failed to resolve '%s' as a valid ref.", replace_ref);
+
+ return replace_object_sha1(object_ref, object, replace_ref, repl, force);
+}
+
+/*
+ * Write the contents of the object named by "sha1" to the file "filename",
+ * pretty-printed for human editing based on its type.
+ */
+static void export_object(const unsigned char *sha1, const char *filename)
+{
+ const char *argv[] = { "--no-replace-objects", "cat-file", "-p", NULL, NULL };
+ struct child_process cmd = { argv };
+ int fd;
+
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno("unable to open %s for writing", filename);
+
+ argv[3] = sha1_to_hex(sha1);
+ cmd.git_cmd = 1;
+ cmd.out = fd;
+
+ if (run_command(&cmd))
+ die("cat-file reported failure");
+
+ close(fd);
+}
+
+/*
+ * Read a previously-exported (and possibly edited) object back from "filename",
+ * interpreting it as "type", and writing the result to the object database.
+ * The sha1 of the written object is returned via sha1.
+ */
+static void import_object(unsigned char *sha1, enum object_type type,
+ const char *filename)
+{
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ die_errno("unable to open %s for reading", filename);
+
+ if (type == OBJ_TREE) {
+ const char *argv[] = { "mktree", NULL };
+ struct child_process cmd = { argv };
+ struct strbuf result = STRBUF_INIT;
+
+ cmd.argv = argv;
+ cmd.git_cmd = 1;
+ cmd.in = fd;
+ cmd.out = -1;
+
+ if (start_command(&cmd))
+ die("unable to spawn mktree");
+
+ if (strbuf_read(&result, cmd.out, 41) < 0)
+ die_errno("unable to read from mktree");
+ close(cmd.out);
+
+ if (finish_command(&cmd))
+ die("mktree reported failure");
+ if (get_sha1_hex(result.buf, sha1) < 0)
+ die("mktree did not return an object name");
+
+ strbuf_release(&result);
+ } else {
+ struct stat st;
+ int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
+
+ if (fstat(fd, &st) < 0)
+ die_errno("unable to fstat %s", filename);
+ if (index_fd(sha1, fd, &st, type, NULL, flags) < 0)
+ die("unable to write object to database");
+ /* index_fd close()s fd for us */
+ }
+
+ /*
+ * No need to close(fd) here; both run-command and index-fd
+ * will have done it for us.
+ */
+}
+
+static int edit_and_replace(const char *object_ref, int force)
+{
+ char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
+ enum object_type type;
+ unsigned char old[20], new[20], prev[20];
+ char ref[PATH_MAX];
+
+ if (get_sha1(object_ref, old) < 0)
+ die("Not a valid object name: '%s'", object_ref);
+
+ type = sha1_object_info(old, NULL);
+ if (type < 0)
+ die("unable to get object type for %s", sha1_to_hex(old));
+
+ check_ref_valid(old, prev, ref, sizeof(ref), force);
+
+ export_object(old, tmpfile);
+ if (launch_editor(tmpfile, NULL, NULL) < 0)
+ die("editing object file failed");
+ import_object(new, type, tmpfile);
+
+ free(tmpfile);
+
+ if (!hashcmp(old, new))
+ return error("new object is the same as the old one: '%s'", sha1_to_hex(old));
+
+ return replace_object_sha1(object_ref, old, "replacement", new, force);
+}
+
int cmd_replace(int argc, const char **argv, const char *prefix)
{
- int list = 0, delete = 0, force = 0;
+ int force = 0;
const char *format = NULL;
+ enum {
+ MODE_UNSPECIFIED = 0,
+ MODE_LIST,
+ MODE_DELETE,
+ MODE_EDIT,
+ MODE_REPLACE
+ } cmdmode = MODE_UNSPECIFIED;
struct option options[] = {
- OPT_BOOL('l', "list", &list, N_("list replace refs")),
- OPT_BOOL('d', "delete", &delete, N_("delete replace refs")),
+ OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
+ OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
+ OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
OPT_END()
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
- if (list && delete)
- usage_msg_opt("-l and -d cannot be used together",
- git_replace_usage, options);
+ if (!cmdmode)
+ cmdmode = argc ? MODE_REPLACE : MODE_LIST;
- if (format && delete)
- usage_msg_opt("--format and -d cannot be used together",
+ if (format && cmdmode != MODE_LIST)
+ usage_msg_opt("--format cannot be used when not listing",
git_replace_usage, options);
- if (force && (list || delete))
- usage_msg_opt("-f cannot be used with -d or -l",
+ if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)
+ usage_msg_opt("-f only makes sense when writing a replacement",
git_replace_usage, options);
- /* Delete refs */
- if (delete) {
+ switch (cmdmode) {
+ case MODE_DELETE:
if (argc < 1)
usage_msg_opt("-d needs at least one argument",
git_replace_usage, options);
return for_each_replace_name(argv, delete_replace_ref);
- }
- /* Replace object */
- if (!list && argc) {
+ case MODE_REPLACE:
if (argc != 2)
usage_msg_opt("bad number of arguments",
git_replace_usage, options);
- if (format)
- usage_msg_opt("--format cannot be used when not listing",
- git_replace_usage, options);
return replace_object(argv[0], argv[1], force);
- }
- /* List refs, even if "list" is not set */
- if (argc > 1)
- usage_msg_opt("only one pattern can be given with -l",
- git_replace_usage, options);
- if (force)
- usage_msg_opt("-f needs some arguments",
- git_replace_usage, options);
+ case MODE_EDIT:
+ if (argc != 1)
+ usage_msg_opt("-e needs exactly one argument",
+ git_replace_usage, options);
+ return edit_and_replace(argv[0], force);
+
+ case MODE_LIST:
+ if (argc > 1)
+ usage_msg_opt("only one pattern can be given with -l",
+ git_replace_usage, options);
+ return list_replace_refs(argv[0], format);
- return list_replace_refs(argv[0], format);
+ default:
+ die("BUG: invalid cmdmode %d", (int)cmdmode);
+ }
}
argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+ git_config(git_xmerge_config, NULL);
+
if (autoupdate == 1)
flags = RERERE_AUTOUPDATE;
if (autoupdate == 0)
if (!get_sha1("HEAD", sha1_orig)) {
orig = sha1_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
- update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+ update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
delete_ref("ORIG_HEAD", old_orig, 0);
set_reflog_message(&msg, "updating HEAD", rev);
- update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+ update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
strbuf_release(&msg);
return update_ref_status;
}
return 0;
}
-static int contains_recurse(struct commit *candidate,
+enum contains_result {
+ CONTAINS_UNKNOWN = -1,
+ CONTAINS_NO = 0,
+ CONTAINS_YES = 1,
+};
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
const struct commit_list *want)
{
- struct commit_list *p;
-
/* was it previously marked as containing a want commit? */
if (candidate->object.flags & TMP_MARK)
return 1;
if (candidate->object.flags & UNINTERESTING)
return 0;
/* or are we it? */
- if (in_commit_list(want, candidate))
+ if (in_commit_list(want, candidate)) {
+ candidate->object.flags |= TMP_MARK;
return 1;
+ }
if (parse_commit(candidate) < 0)
return 0;
- /* Otherwise recurse and mark ourselves for future traversals. */
- for (p = candidate->parents; p; p = p->next) {
- if (contains_recurse(p->item, want)) {
- candidate->object.flags |= TMP_MARK;
- return 1;
- }
- }
- candidate->object.flags |= UNINTERESTING;
- return 0;
+ return -1;
}
-static int contains(struct commit *candidate, const struct commit_list *want)
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct stack {
+ int nr, alloc;
+ struct stack_entry {
+ struct commit *commit;
+ struct commit_list *parents;
+ } *stack;
+};
+
+static void push_to_stack(struct commit *candidate, struct stack *stack)
+{
+ int index = stack->nr++;
+ ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
+ stack->stack[index].commit = candidate;
+ stack->stack[index].parents = candidate->parents;
+}
+
+static enum contains_result contains(struct commit *candidate,
+ const struct commit_list *want)
{
- return contains_recurse(candidate, want);
+ struct stack stack = { 0, 0, NULL };
+ int result = contains_test(candidate, want);
+
+ if (result != CONTAINS_UNKNOWN)
+ return result;
+
+ push_to_stack(candidate, &stack);
+ while (stack.nr) {
+ struct stack_entry *entry = &stack.stack[stack.nr - 1];
+ struct commit *commit = entry->commit;
+ struct commit_list *parents = entry->parents;
+
+ if (!parents) {
+ commit->object.flags |= UNINTERESTING;
+ stack.nr--;
+ }
+ /*
+ * If we just popped the stack, parents->item has been marked,
+ * therefore contains_test will return a meaningful 0 or 1.
+ */
+ else switch (contains_test(parents->item, want)) {
+ case CONTAINS_YES:
+ commit->object.flags |= TMP_MARK;
+ stack.nr--;
+ break;
+ case CONTAINS_NO:
+ entry->parents = parents->next;
+ break;
+ case CONTAINS_UNKNOWN:
+ push_to_stack(parents->item, &stack);
+ break;
+ }
+ }
+ free(stack.stack);
+ return contains_test(candidate, want);
}
static void show_tag_lines(const unsigned char *sha1, int lines)
}
static const char tag_template[] =
- N_("\nWrite a tag message\n"
+ N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be ignored.\n");
static const char tag_template_nocleanup[] =
- N_("\nWrite a tag message\n"
+ N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be kept; you may remove them"
" yourself if you want to.\n");
struct strbuf buf = STRBUF_INIT;
strbuf_addch(&buf, '\n');
if (opt->cleanup_mode == CLEANUP_ALL)
- strbuf_commented_addf(&buf, _(tag_template), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
else
- strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
write_or_die(fd, buf.buf, buf.len);
strbuf_release(&buf);
}
unsigned long ul;
char *endp;
+ if (!arg)
+ return -1;
+
errno = 0;
ul = strtoul(arg, &endp, 8);
if (errno || endp == arg || *endp != ',' || (unsigned int) ul != ul)
NULL
};
-static int updates_alloc;
-static int updates_count;
-static const struct ref_update **updates;
+static struct ref_transaction *transaction;
static char line_termination = '\n';
static int update_flags;
-static struct ref_update *update_alloc(void)
-{
- struct ref_update *update;
-
- /* Allocate and zero-init a struct ref_update */
- update = xcalloc(1, sizeof(*update));
- ALLOC_GROW(updates, updates_count + 1, updates_alloc);
- updates[updates_count++] = update;
-
- /* Store and reset accumulated options */
- update->flags = update_flags;
- update_flags = 0;
-
- return update;
-}
-
-static void update_store_ref_name(struct ref_update *update,
- const char *ref_name)
-{
- if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
- die("invalid ref format: %s", ref_name);
- update->ref_name = xstrdup(ref_name);
-}
-
-static void update_store_new_sha1(struct ref_update *update,
- const char *newvalue)
-{
- if (*newvalue && get_sha1(newvalue, update->new_sha1))
- die("invalid new value for ref %s: %s",
- update->ref_name, newvalue);
-}
-
-static void update_store_old_sha1(struct ref_update *update,
- const char *oldvalue)
-{
- if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
- die("invalid old value for ref %s: %s",
- update->ref_name, oldvalue);
-
- /* We have an old value if non-empty, or if empty without -z */
- update->have_old = *oldvalue || line_termination;
-}
-
+/*
+ * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
+ * and append the result to arg. Return a pointer to the terminator.
+ * Die if there is an error in how the argument is C-quoted. This
+ * function is only used if not -z.
+ */
static const char *parse_arg(const char *next, struct strbuf *arg)
{
- /* Parse SP-terminated, possibly C-quoted argument */
- if (*next != '"')
+ if (*next == '"') {
+ const char *orig = next;
+
+ if (unquote_c_style(arg, next, &next))
+ die("badly quoted argument: %s", orig);
+ if (*next && !isspace(*next))
+ die("unexpected character after quoted argument: %s", orig);
+ } else {
while (*next && !isspace(*next))
strbuf_addch(arg, *next++);
- else if (unquote_c_style(arg, next, &next))
- die("badly quoted argument: %s", next);
+ }
- /* Return position after the argument */
return next;
}
-static const char *parse_first_arg(const char *next, struct strbuf *arg)
+/*
+ * Parse the reference name immediately after "command SP". If not
+ * -z, then handle C-quoting. Return a pointer to a newly allocated
+ * string containing the name of the reference, or NULL if there was
+ * an error. Update *next to point at the character that terminates
+ * the argument. Die if C-quoting is malformed or the reference name
+ * is invalid.
+ */
+static char *parse_refname(struct strbuf *input, const char **next)
{
- /* Parse argument immediately after "command SP" */
- strbuf_reset(arg);
+ struct strbuf ref = STRBUF_INIT;
+
if (line_termination) {
/* Without -z, use the next argument */
- next = parse_arg(next, arg);
+ *next = parse_arg(*next, &ref);
} else {
- /* With -z, use rest of first NUL-terminated line */
- strbuf_addstr(arg, next);
- next = next + arg->len;
+ /* With -z, use everything up to the next NUL */
+ strbuf_addstr(&ref, *next);
+ *next += ref.len;
}
- return next;
+
+ if (!ref.len) {
+ strbuf_release(&ref);
+ return NULL;
+ }
+
+ if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", ref.buf);
+
+ return strbuf_detach(&ref, NULL);
}
-static const char *parse_next_arg(const char *next, struct strbuf *arg)
+/*
+ * The value being parsed is <oldvalue> (as opposed to <newvalue>; the
+ * difference affects which error messages are generated):
+ */
+#define PARSE_SHA1_OLD 0x01
+
+/*
+ * For backwards compatibility, accept an empty string for update's
+ * <newvalue> in binary mode to be equivalent to specifying zeros.
+ */
+#define PARSE_SHA1_ALLOW_EMPTY 0x02
+
+/*
+ * Parse an argument separator followed by the next argument, if any.
+ * If there is an argument, convert it to a SHA-1, write it to sha1,
+ * set *next to point at the character terminating the argument, and
+ * return 0. If there is no argument at all (not even the empty
+ * string), return 1 and leave *next unchanged. If the value is
+ * provided but cannot be converted to a SHA-1, die. flags can
+ * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
+ */
+static int parse_next_sha1(struct strbuf *input, const char **next,
+ unsigned char *sha1,
+ const char *command, const char *refname,
+ int flags)
{
- /* Parse next SP-terminated or NUL-terminated argument, if any */
- strbuf_reset(arg);
+ struct strbuf arg = STRBUF_INIT;
+ int ret = 0;
+
+ if (*next == input->buf + input->len)
+ goto eof;
+
if (line_termination) {
/* Without -z, consume SP and use next argument */
- if (!*next)
- return NULL;
- if (*next != ' ')
- die("expected SP but got: %s", next);
- next = parse_arg(next + 1, arg);
+ if (!**next || **next == line_termination)
+ return 1;
+ if (**next != ' ')
+ die("%s %s: expected SP but got: %s",
+ command, refname, *next);
+ (*next)++;
+ *next = parse_arg(*next, &arg);
+ if (arg.len) {
+ if (get_sha1(arg.buf, sha1))
+ goto invalid;
+ } else {
+ /* Without -z, an empty value means all zeros: */
+ hashclr(sha1);
+ }
} else {
/* With -z, read the next NUL-terminated line */
- if (*next)
- die("expected NUL but got: %s", next);
- if (strbuf_getline(arg, stdin, '\0') == EOF)
- return NULL;
- next = arg->buf + arg->len;
+ if (**next)
+ die("%s %s: expected NUL but got: %s",
+ command, refname, *next);
+ (*next)++;
+ if (*next == input->buf + input->len)
+ goto eof;
+ strbuf_addstr(&arg, *next);
+ *next += arg.len;
+
+ if (arg.len) {
+ if (get_sha1(arg.buf, sha1))
+ goto invalid;
+ } else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
+ /* With -z, treat an empty value as all zeros: */
+ warning("%s %s: missing <newvalue>, treating as zero",
+ command, refname);
+ hashclr(sha1);
+ } else {
+ /*
+ * With -z, an empty non-required value means
+ * unspecified:
+ */
+ ret = 1;
+ }
}
- return next;
+
+ strbuf_release(&arg);
+
+ return ret;
+
+ invalid:
+ die(flags & PARSE_SHA1_OLD ?
+ "%s %s: invalid <oldvalue>: %s" :
+ "%s %s: invalid <newvalue>: %s",
+ command, refname, arg.buf);
+
+ eof:
+ die(flags & PARSE_SHA1_OLD ?
+ "%s %s: unexpected end of input when reading <oldvalue>" :
+ "%s %s: unexpected end of input when reading <newvalue>",
+ command, refname);
}
-static void parse_cmd_update(const char *next)
+
+/*
+ * The following five parse_cmd_*() functions parse the corresponding
+ * command. In each case, next points at the character following the
+ * command name and the following space. They each return a pointer
+ * to the character terminating the command, and die with an
+ * explanatory message if there are any parsing problems. All of
+ * these functions handle either text or binary format input,
+ * depending on how line_termination is set.
+ */
+
+static const char *parse_cmd_update(struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf newvalue = STRBUF_INIT;
- struct strbuf oldvalue = STRBUF_INIT;
- struct ref_update *update;
+ char *refname;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int have_old;
- update = update_alloc();
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("update: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("update line missing <ref>");
+ if (parse_next_sha1(input, &next, new_sha1, "update", refname,
+ PARSE_SHA1_ALLOW_EMPTY))
+ die("update %s: missing <newvalue>", refname);
- if ((next = parse_next_arg(next, &newvalue)) != NULL)
- update_store_new_sha1(update, newvalue.buf);
- else
- die("update %s missing <newvalue>", ref.buf);
+ have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
+ PARSE_SHA1_OLD);
- if ((next = parse_next_arg(next, &oldvalue)) != NULL)
- update_store_old_sha1(update, oldvalue.buf);
- else if(!line_termination)
- die("update %s missing [<oldvalue>] NUL", ref.buf);
+ if (*next != line_termination)
+ die("update %s: extra input: %s", refname, next);
- if (next && *next)
- die("update %s has extra input: %s", ref.buf, next);
+ ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, have_old);
+
+ update_flags = 0;
+ free(refname);
+
+ return next;
}
-static void parse_cmd_create(const char *next)
+static const char *parse_cmd_create(struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf newvalue = STRBUF_INIT;
- struct ref_update *update;
+ char *refname;
+ unsigned char new_sha1[20];
- update = update_alloc();
- update->have_old = 1;
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("create: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("create line missing <ref>");
+ if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
+ die("create %s: missing <newvalue>", refname);
- if ((next = parse_next_arg(next, &newvalue)) != NULL)
- update_store_new_sha1(update, newvalue.buf);
- else
- die("create %s missing <newvalue>", ref.buf);
- if (is_null_sha1(update->new_sha1))
- die("create %s given zero new value", ref.buf);
+ if (is_null_sha1(new_sha1))
+ die("create %s: zero <newvalue>", refname);
+
+ if (*next != line_termination)
+ die("create %s: extra input: %s", refname, next);
- if (next && *next)
- die("create %s has extra input: %s", ref.buf, next);
+ ref_transaction_create(transaction, refname, new_sha1, update_flags);
+
+ update_flags = 0;
+ free(refname);
+
+ return next;
}
-static void parse_cmd_delete(const char *next)
+static const char *parse_cmd_delete(struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf oldvalue = STRBUF_INIT;
- struct ref_update *update;
+ char *refname;
+ unsigned char old_sha1[20];
+ int have_old;
- update = update_alloc();
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("delete: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("delete line missing <ref>");
+ if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
+ PARSE_SHA1_OLD)) {
+ have_old = 0;
+ } else {
+ if (is_null_sha1(old_sha1))
+ die("delete %s: zero <oldvalue>", refname);
+ have_old = 1;
+ }
+
+ if (*next != line_termination)
+ die("delete %s: extra input: %s", refname, next);
- if ((next = parse_next_arg(next, &oldvalue)) != NULL)
- update_store_old_sha1(update, oldvalue.buf);
- else if(!line_termination)
- die("delete %s missing [<oldvalue>] NUL", ref.buf);
- if (update->have_old && is_null_sha1(update->old_sha1))
- die("delete %s given zero old value", ref.buf);
+ ref_transaction_delete(transaction, refname, old_sha1,
+ update_flags, have_old);
- if (next && *next)
- die("delete %s has extra input: %s", ref.buf, next);
+ update_flags = 0;
+ free(refname);
+
+ return next;
}
-static void parse_cmd_verify(const char *next)
+static const char *parse_cmd_verify(struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf value = STRBUF_INIT;
- struct ref_update *update;
+ char *refname;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int have_old;
+
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("verify: missing <ref>");
+
+ if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
+ PARSE_SHA1_OLD)) {
+ hashclr(new_sha1);
+ have_old = 0;
+ } else {
+ hashcpy(new_sha1, old_sha1);
+ have_old = 1;
+ }
- update = update_alloc();
+ if (*next != line_termination)
+ die("verify %s: extra input: %s", refname, next);
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("verify line missing <ref>");
+ ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, have_old);
- if ((next = parse_next_arg(next, &value)) != NULL) {
- update_store_old_sha1(update, value.buf);
- update_store_new_sha1(update, value.buf);
- } else if(!line_termination)
- die("verify %s missing [<oldvalue>] NUL", ref.buf);
+ update_flags = 0;
+ free(refname);
- if (next && *next)
- die("verify %s has extra input: %s", ref.buf, next);
+ return next;
}
-static void parse_cmd_option(const char *next)
+static const char *parse_cmd_option(struct strbuf *input, const char *next)
{
- if (!strcmp(next, "no-deref"))
+ if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
update_flags |= REF_NODEREF;
else
die("option unknown: %s", next);
+ return next + 8;
}
static void update_refs_stdin(void)
{
- struct strbuf cmd = STRBUF_INIT;
+ struct strbuf input = STRBUF_INIT;
+ const char *next;
+ if (strbuf_read(&input, 0, 1000) < 0)
+ die_errno("could not read from stdin");
+ next = input.buf;
/* Read each line dispatch its command */
- while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
- if (!cmd.buf[0])
+ while (next < input.buf + input.len) {
+ if (*next == line_termination)
die("empty command in input");
- else if (isspace(*cmd.buf))
- die("whitespace before command: %s", cmd.buf);
- else if (starts_with(cmd.buf, "update "))
- parse_cmd_update(cmd.buf + 7);
- else if (starts_with(cmd.buf, "create "))
- parse_cmd_create(cmd.buf + 7);
- else if (starts_with(cmd.buf, "delete "))
- parse_cmd_delete(cmd.buf + 7);
- else if (starts_with(cmd.buf, "verify "))
- parse_cmd_verify(cmd.buf + 7);
- else if (starts_with(cmd.buf, "option "))
- parse_cmd_option(cmd.buf + 7);
+ else if (isspace(*next))
+ die("whitespace before command: %s", next);
+ else if (starts_with(next, "update "))
+ next = parse_cmd_update(&input, next + 7);
+ else if (starts_with(next, "create "))
+ next = parse_cmd_create(&input, next + 7);
+ else if (starts_with(next, "delete "))
+ next = parse_cmd_delete(&input, next + 7);
+ else if (starts_with(next, "verify "))
+ next = parse_cmd_verify(&input, next + 7);
+ else if (starts_with(next, "option "))
+ next = parse_cmd_option(&input, next + 7);
else
- die("unknown command: %s", cmd.buf);
+ die("unknown command: %s", next);
+
+ next++;
+ }
- strbuf_release(&cmd);
+ strbuf_release(&input);
}
int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with empty message.");
if (read_stdin) {
+ int ret;
+ transaction = ref_transaction_begin();
+
if (delete || no_deref || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
update_refs_stdin();
- return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+ ret = ref_transaction_commit(transaction, msg,
+ UPDATE_REFS_DIE_ON_ERR);
+ return ret;
}
if (end_null)
return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
- flags, DIE_ON_ERR);
+ flags, UPDATE_REFS_DIE_ON_ERR);
}
#define S_IFGITLINK 0160000
#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
+/*
+ * Some mode bits are also used internally for computations.
+ *
+ * They *must* not overlap with any valid modes, and they *must* not be emitted
+ * to outside world - i.e. appear on disk or network. In other words, it's just
+ * temporary fields, which we internally use, but they have to stay in-house.
+ *
+ * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git
+ * codebase mode is `unsigned int` which is assumed to be at least 32 bits )
+ */
+
+/* used internally in tree-diff */
+#define S_DIFFTREE_IFXMIN_NEQ 0x80000000
+
+
/*
* Intensive research over the course of many years has shown that
* port 9418 is totally unused by anything else. Or
initialized : 1;
struct hashmap name_hash;
struct hashmap dir_hash;
+ unsigned char sha1[20];
};
extern struct index_state the_index;
* that is subject to stripspace.
*/
extern char comment_line_char;
+extern int auto_comment_line_char;
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
-int offset_1st_component(const char *path);
/* object replacement */
#define LOOKUP_REPLACE_OBJECT 1
*/
extern int split_ident_line(struct ident_split *, const char *, int);
+/*
+ * Like show_date, but pull the timestamp and tz parameters from
+ * the ident_split. It will also sanity-check the values and produce
+ * a well-known sentinel date if they appear bogus.
+ */
+const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
+
/*
* Compare split idents for equality or strict ordering. Note that we
* compare only the ident part of the line, ignoring any timestamp.
extern int git_env_bool(const char *, int);
extern int git_config_system(void);
extern int config_error_nonbool(const char *);
-#if defined(__GNUC__) && ! defined(__clang__)
-#define config_error_nonbool(s) (config_error_nonbool(s), -1)
+#if defined(__GNUC__)
+#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
#endif
extern const char *get_log_output_encoding(void);
extern const char *get_commit_output_encoding(void);
extern ssize_t read_in_full(int fd, void *buf, size_t count);
extern ssize_t write_in_full(int fd, const void *buf, size_t count);
+extern ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
+
static inline ssize_t write_str_in_full(int fd, const char *str)
{
return write_in_full(fd, str, strlen(str));
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;
+ struct argv_array *argv;
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;
+ memset(&column_process, 0, sizeof(column_process));
+ argv = &column_process.args;
+
+ argv_array_push(argv, "column");
+ argv_array_pushf(argv, "--raw-mode=%d", colopts);
+ if (opts && opts->width)
+ argv_array_pushf(argv, "--width=%d", opts->width);
+ if (opts && opts->indent)
+ argv_array_pushf(argv, "--indent=%s", opts->indent);
+ if (opts && opts->padding)
+ argv_array_pushf(argv, "--padding=%d", opts->padding);
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)
+ if (start_command(&column_process))
return -2;
fd_out = dup(1);
return path->path;
}
+
+/* find set of paths that every parent touches */
+static struct combine_diff_path *find_paths_generic(const unsigned char *sha1,
+ const struct sha1_array *parents, struct diff_options *opt)
+{
+ struct combine_diff_path *paths = NULL;
+ int i, num_parent = parents->nr;
+
+ int output_format = opt->output_format;
+ const char *orderfile = opt->orderfile;
+
+ opt->output_format = DIFF_FORMAT_NO_OUTPUT;
+ /* tell diff_tree to emit paths in sorted (=tree) order */
+ opt->orderfile = NULL;
+
+ /* D(A,P1...Pn) = D(A,P1) ^ ... ^ D(A,Pn) (wrt paths) */
+ for (i = 0; i < num_parent; i++) {
+ /*
+ * show stat against the first parent even when doing
+ * combined diff.
+ */
+ int stat_opt = (output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ if (i == 0 && stat_opt)
+ opt->output_format = stat_opt;
+ else
+ opt->output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_tree_sha1(parents->sha1[i], sha1, "", opt);
+ diffcore_std(opt);
+ paths = intersect_paths(paths, i, num_parent);
+
+ /* if showing diff, show it in requested order */
+ if (opt->output_format != DIFF_FORMAT_NO_OUTPUT &&
+ orderfile) {
+ diffcore_order(orderfile);
+ }
+
+ diff_flush(opt);
+ }
+
+ opt->output_format = output_format;
+ opt->orderfile = orderfile;
+ return paths;
+}
+
+
+/*
+ * find set of paths that everybody touches, assuming diff is run without
+ * rename/copy detection, etc, comparing all trees simultaneously (= faster).
+ */
+static struct combine_diff_path *find_paths_multitree(
+ const unsigned char *sha1, const struct sha1_array *parents,
+ struct diff_options *opt)
+{
+ int i, nparent = parents->nr;
+ const unsigned char **parents_sha1;
+ struct combine_diff_path paths_head;
+ struct strbuf base;
+
+ parents_sha1 = xmalloc(nparent * sizeof(parents_sha1[0]));
+ for (i = 0; i < nparent; i++)
+ parents_sha1[i] = parents->sha1[i];
+
+ /* fake list head, so worker can assume it is non-NULL */
+ paths_head.next = NULL;
+
+ strbuf_init(&base, PATH_MAX);
+ diff_tree_paths(&paths_head, sha1, parents_sha1, nparent, &base, opt);
+
+ strbuf_release(&base);
+ free(parents_sha1);
+ return paths_head.next;
+}
+
+
void diff_tree_combined(const unsigned char *sha1,
const struct sha1_array *parents,
int dense,
{
struct diff_options *opt = &rev->diffopt;
struct diff_options diffopts;
- struct combine_diff_path *p, *paths = NULL;
+ struct combine_diff_path *p, *paths;
int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
+ int need_generic_pathscan;
+
+ /* nothing to do, if no parents */
+ if (!num_parent)
+ return;
+
+ show_log_first = !!rev->loginfo && !rev->no_commit_id;
+ needsep = 0;
+ if (show_log_first) {
+ show_log(rev);
+
+ if (rev->verbose_header && opt->output_format &&
+ opt->output_format != DIFF_FORMAT_NO_OUTPUT)
+ printf("%s%c", diff_line_prefix(opt),
+ opt->line_termination);
+ }
diffopts = *opt;
copy_pathspec(&diffopts.pathspec, &opt->pathspec);
- diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(&diffopts, RECURSIVE);
DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
- /* tell diff_tree to emit paths in sorted (=tree) order */
- diffopts.orderfile = NULL;
- show_log_first = !!rev->loginfo && !rev->no_commit_id;
- needsep = 0;
- /* find set of paths that everybody touches */
- for (i = 0; i < num_parent; i++) {
- /* show stat against the first parent even
+ /* find set of paths that everybody touches
+ *
+ * NOTE
+ *
+ * Diffcore transformations are bound to diff_filespec and logic
+ * comparing two entries - i.e. they do not apply directly to combine
+ * diff.
+ *
+ * If some of such transformations is requested - we launch generic
+ * path scanning, which works significantly slower compared to
+ * simultaneous all-trees-in-one-go scan in find_paths_multitree().
+ *
+ * TODO some of the filters could be ported to work on
+ * combine_diff_paths - i.e. all functionality that skips paths, so in
+ * theory, we could end up having only multitree path scanning.
+ *
+ * NOTE please keep this semantically in sync with diffcore_std()
+ */
+ need_generic_pathscan = opt->skip_stat_unmatch ||
+ DIFF_OPT_TST(opt, FOLLOW_RENAMES) ||
+ opt->break_opt != -1 ||
+ opt->detect_rename ||
+ opt->pickaxe ||
+ opt->filter;
+
+
+ if (need_generic_pathscan) {
+ /*
+ * NOTE generic case also handles --stat, as it computes
+ * diff(sha1,parent_i) for all i to do the job, specifically
+ * for parent0.
+ */
+ paths = find_paths_generic(sha1, parents, &diffopts);
+ }
+ else {
+ int stat_opt;
+ paths = find_paths_multitree(sha1, parents, &diffopts);
+
+ /*
+ * show stat against the first parent even
* when doing combined diff.
*/
- int stat_opt = (opt->output_format &
+ stat_opt = (opt->output_format &
(DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
- if (i == 0 && stat_opt)
+ if (stat_opt) {
diffopts.output_format = stat_opt;
- else
- diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_tree_sha1(parents->sha1[i], sha1, "", &diffopts);
- diffcore_std(&diffopts);
- paths = intersect_paths(paths, i, num_parent);
- if (show_log_first && i == 0) {
- show_log(rev);
-
- if (rev->verbose_header && opt->output_format)
- printf("%s%c", diff_line_prefix(opt),
- opt->line_termination);
+ diff_tree_sha1(parents->sha1[0], sha1, "", &diffopts);
+ diffcore_std(&diffopts);
+ if (opt->orderfile)
+ diffcore_order(opt->orderfile);
+ diff_flush(&diffopts);
}
-
- /* if showing diff, show it in requested order */
- if (diffopts.output_format != DIFF_FORMAT_NO_OUTPUT &&
- opt->orderfile) {
- diffcore_order(opt->orderfile);
- }
-
- diff_flush(&diffopts);
}
/* find out number of surviving paths */
errno = EINVAL;
return -1;
}
+
+int mingw_offset_1st_component(const char *path)
+{
+ int offset = 0;
+ if (has_dos_drive_prefix(path))
+ offset = 2;
+
+ /* unc paths */
+ else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
+
+ /* skip server name */
+ char *pos = strpbrk(path + 2, "\\/");
+ if (!pos)
+ return 0; /* Error: malformed unc path */
+
+ do {
+ pos++;
+ } while (*pos && !is_dir_sep(*pos));
+
+ offset = pos - path;
+ }
+
+ return offset + is_dir_sep(path[offset]);
+}
return ret;
}
#define find_last_dir_sep mingw_find_last_dir_sep
+int mingw_offset_1st_component(const char *path);
+#define offset_1st_component mingw_offset_1st_component
#define PATH_SEP ';'
#define PRIuMAX "I64u"
#define PRId64 "I64d"
}
while (n < length) {
- ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
+ ssize_t count = xpread(fd, (char *)start + n, length - n, offset + n);
if (count == 0) {
memset((char *)start+n, 0, length-n);
}
if (count < 0) {
- if (errno == EAGAIN || errno == EINTR)
- continue;
free(start);
errno = EACCES;
return MAP_FAILED;
if (!rc && timeout == INFTIM)
{
- SwitchToThread();
+ SleepEx (1, TRUE);
goto restart;
}
+++ /dev/null
-#include <malloc.h>
--- /dev/null
+#include <malloc.h>
if (!strcmp(var, "core.commentchar")) {
const char *comment;
int ret = git_config_string(&comment, var, value);
- if (!ret)
+ if (ret)
+ return ret;
+ else if (!strcasecmp(comment, "auto"))
+ auto_comment_line_char = 1;
+ else if (comment[0] && !comment[1]) {
comment_line_char = comment[0];
- return ret;
+ auto_comment_line_char = 0;
+ } else
+ return error("core.commentChar should only be one character");
+ return 0;
}
if (!strcmp(var, "core.askpass"))
MAP_PRIVATE, in_fd, 0);
close(in_fd);
+ if (fchmod(fd, st.st_mode & 07777) < 0) {
+ error("fchmod on %s failed: %s",
+ lock->filename, strerror(errno));
+ ret = CONFIG_NO_WRITE;
+ goto out_free;
+ }
+
if (store.seen == 0)
store.seen = 1;
int out_fd;
char buf[1024];
FILE *config_file;
+ struct stat st;
if (new_name && !section_name_is_ok(new_name)) {
ret = error("invalid section name: %s", new_name);
goto unlock_and_out;
}
+ fstat(fileno(config_file), &st);
+
+ if (fchmod(out_fd, st.st_mode & 07777) < 0) {
+ ret = error("fchmod on %s failed: %s",
+ lock->filename, strerror(errno));
+ goto out;
+ }
+
while (fgets(buf, sizeof(buf), config_file)) {
int i;
int length;
NO_NSEC = YesPlease
endif
ifeq ($(uname_S),Linux)
+ HAVE_ALLOCA_H = YesPlease
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
HAVE_DEV_TTY = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
+ HAVE_ALLOCA_H = YesPlease
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
NEEDS_NSL = YesPlease
SHELL_PATH = /bin/bash
SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin
+ HAVE_ALLOCA_H = YesPlease
NO_STRCASESTR = YesPlease
NO_MEMMEM = YesPlease
NO_MKDTEMP = YesPlease
endif
INSTALL = /usr/ucb/install
TAR = gtar
- BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H
+ BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__
endif
ifeq ($(uname_O),Cygwin)
ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4)
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
OLD_ICONV = UnfortunatelyYes
- NO_THREAD_SAFE_PREAD = YesPlease
# There are conflicting reports about this.
# On some boxes NO_MMAP is needed, and not so elsewhere.
# Try commenting this out if you suspect MMAP is more efficient
else
NO_REGEX = UnfortunatelyYes
endif
+ HAVE_ALLOCA_H = YesPlease
NEEDS_LIBICONV = YesPlease
NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
endif
ifeq ($(uname_S),GNU)
# GNU/Hurd
+ HAVE_ALLOCA_H = YesPlease
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
ifeq ($(uname_S),Windows)
GIT_VERSION := $(GIT_VERSION).MSVC
pathsep = ;
+ HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
COMPAT_OBJS = compat/msvc.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
- COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
- BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
+ COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+ BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj
PTHREAD_LIBS =
lib =
ifndef DEBUG
- BASIC_CFLAGS += -GL -Os -MT
+ BASIC_CFLAGS += -GL -Os -MD
BASIC_LDFLAGS += -LTCG
AR += -LTCG
else
- BASIC_CFLAGS += -Zi -MTd
+ BASIC_CFLAGS += -Zi -MDd
endif
X = .exe
endif
endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
+ HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
GIT_CONF_SUBST([LIBPCREDIR])
fi)
#
+# Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.
+AC_FUNC_ALLOCA
+case $ac_cv_working_alloca_h in
+ yes) HAVE_ALLOCA_H=YesPlease;;
+ *) HAVE_ALLOCA_H='';;
+esac
+GIT_CONF_SUBST([HAVE_ALLOCA_H])
+#
# 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://
# transports.
static struct child_process *git_proxy_connect(int fd[2], char *host)
{
const char *port = STR(DEFAULT_GIT_PORT);
- const char **argv;
struct child_process *proxy;
get_host_and_port(&host, &port);
- argv = xmalloc(sizeof(*argv) * 4);
- argv[0] = git_proxy_command;
- argv[1] = host;
- argv[2] = port;
- argv[3] = NULL;
proxy = xcalloc(1, sizeof(*proxy));
- proxy->argv = argv;
+ argv_array_push(&proxy->args, git_proxy_command);
+ argv_array_push(&proxy->args, host);
+ argv_array_push(&proxy->args, port);
proxy->in = -1;
proxy->out = -1;
if (start_command(proxy))
- die("cannot start proxy %s", argv[0]);
+ die("cannot start proxy %s", git_proxy_command);
fd[0] = proxy->out; /* read from proxy stdout */
fd[1] = proxy->in; /* write to proxy stdin */
return proxy;
char *hostandport, *path;
struct child_process *conn = &no_fork;
enum protocol protocol;
- const char **arg;
struct strbuf cmd = STRBUF_INIT;
/* Without this we cannot rely on waitpid() to tell
sq_quote_buf(&cmd, path);
conn->in = conn->out = -1;
- conn->argv = arg = xcalloc(7, sizeof(*arg));
if (protocol == PROTO_SSH) {
const char *ssh = getenv("GIT_SSH");
int putty = ssh && strcasestr(ssh, "plink");
if (!ssh) ssh = "ssh";
- *arg++ = ssh;
+ argv_array_push(&conn->args, ssh);
if (putty && !strcasestr(ssh, "tortoiseplink"))
- *arg++ = "-batch";
+ argv_array_push(&conn->args, "-batch");
if (port) {
/* P is for PuTTY, p is for OpenSSH */
- *arg++ = putty ? "-P" : "-p";
- *arg++ = port;
+ argv_array_push(&conn->args, putty ? "-P" : "-p");
+ argv_array_push(&conn->args, port);
}
- *arg++ = ssh_host;
+ argv_array_push(&conn->args, ssh_host);
} else {
/* remove repo-local variables from the environment */
conn->env = local_repo_env;
conn->use_shell = 1;
}
- *arg++ = cmd.buf;
- *arg = NULL;
+ argv_array_push(&conn->args, cmd.buf);
if (start_command(conn))
die("unable to fork");
return 0;
code = finish_command(conn);
- free(conn->argv);
free(conn);
return code;
}
if [[ -n "$count" && -n "$name" ]]; then
__git_ps1_upstream_name=$(git rev-parse \
--abbrev-ref "$upstream" 2>/dev/null)
- if [ $pcmode = yes ]; then
- # see the comments around the
- # __git_ps1_branch_name variable below
+ if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
p="$p \${__git_ps1_upstream_name}"
else
p="$p ${__git_ps1_upstream_name}"
;;
esac
+ # ps1_expanded: This variable is set to 'yes' if the shell
+ # subjects the value of PS1 to parameter expansion:
+ #
+ # * bash does unless the promptvars option is disabled
+ # * zsh does not unless the PROMPT_SUBST option is set
+ # * POSIX shells always do
+ #
+ # If the shell would expand the contents of PS1 when drawing
+ # the prompt, a raw ref name must not be included in PS1.
+ # This protects the user from arbitrary code execution via
+ # specially crafted ref names. For example, a ref named
+ # 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
+ # shell to execute 'sudo rm -rf /' when the prompt is drawn.
+ #
+ # Instead, the ref name should be placed in a separate global
+ # variable (in the __git_ps1_* namespace to avoid colliding
+ # with the user's environment) and that variable should be
+ # referenced from PS1. For example:
+ #
+ # __git_ps1_foo=$(do_something_to_get_ref_name)
+ # PS1="...stuff...\${__git_ps1_foo}...stuff..."
+ #
+ # If the shell does not expand the contents of PS1, the raw
+ # ref name must be included in PS1.
+ #
+ # The value of this variable is only relevant when in pcmode.
+ #
+ # Assume that the shell follows the POSIX specification and
+ # expands PS1 unless determined otherwise. (This is more
+ # likely to be correct if the user has a non-bash, non-zsh
+ # shell and safer than the alternative if the assumption is
+ # incorrect.)
+ #
+ local ps1_expanded=yes
+ [ -z "$ZSH_VERSION" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
+ [ -z "$BASH_VERSION" ] || shopt -q promptvars || ps1_expanded=no
+
local repo_info rev_parse_exit_code
repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
--is-bare-repository --is-inside-work-tree \
fi
b=${b##refs/heads/}
- if [ $pcmode = yes ]; then
- # In pcmode (and only pcmode) the contents of
- # $gitstring are subject to expansion by the shell.
- # Avoid putting the raw ref name in the prompt to
- # protect the user from arbitrary code execution via
- # specially crafted ref names (e.g., a ref named
- # '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' would execute
- # 'sudo rm -rf /' when the prompt is drawn). Instead,
- # put the ref name in a new global variable (in the
- # __git_ps1_* namespace to avoid colliding with the
- # user's environment) and reference that variable from
- # PS1.
+ if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
__git_ps1_branch_name=$b
- # note that the $ is escaped -- the variable will be
- # expanded later (when it's time to draw the prompt)
b="\${__git_ps1_branch_name}"
fi
all: git-credential-wincred.exe
-CC = gcc
-RM = rm -f
-CFLAGS = -O2 -Wall
-
-include ../../../config.mak.autogen
-include ../../../config.mak
+CC ?= gcc
+RM ?= rm -f
+CFLAGS ?= -O2 -Wall
+
+prefix ?= /usr/local
+libexecdir ?= $(prefix)/libexec/git-core
+
+INSTALL ?= install
+
git-credential-wincred.exe : git-credential-wincred.c
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
+install: git-credential-wincred.exe
+ $(INSTALL) -m 755 $^ $(libexecdir)
+
clean:
$(RM) git-credential-wincred.exe
+++ /dev/null
-The git-diffall script provides a directory based diff mechanism
-for git.
-
-To determine what diff viewer is used, the script requires either
-the 'diff.tool' or 'merge.tool' configuration option to be set.
-
-This script is compatible with most common forms used to specify a
-range of revisions to diff:
-
- 1. git diffall: shows diff between working tree and staged changes
- 2. git diffall --cached [<commit>]: shows diff between staged
- changes and HEAD (or other named commit)
- 3. git diffall <commit>: shows diff between working tree and named
- commit
- 4. git diffall <commit> <commit>: show diff between two named commits
- 5. git diffall <commit>..<commit>: same as above
- 6. git diffall <commit>...<commit>: show the changes on the branch
- containing and up to the second, starting at a common ancestor
- of both <commit>
-
-Note: all forms take an optional path limiter [-- <path>*]
-
-The '--extcmd=<command>' option allows the user to specify a custom
-command for viewing diffs. When given, configured defaults are
-ignored and the script runs $command $LOCAL $REMOTE. Additionally,
-$BASE is set in the environment.
-
-This script is based on an example provided by Thomas Rast on the
-Git list [1]:
-
-[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
+++ /dev/null
-#!/bin/sh
-# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
-#
-# Perform a directory diff between commits in the repository using
-# the external diff or merge tool specified in the user's config.
-
-USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
-
- --cached Compare to the index rather than the working tree.
-
- --copy-back Copy files back to the working tree when the diff
- tool exits (in case they were modified by the
- user). This option is only valid if the diff
- compared with the working tree.
-
- -x=<command>
- --extcmd=<command> Specify a custom command for viewing diffs.
- git-diffall ignores the configured defaults and
- runs $command $LOCAL $REMOTE when this option is
- specified. Additionally, $BASE is set in the
- environment.
-'
-
-SUBDIRECTORY_OK=1
-. "$(git --exec-path)/git-sh-setup"
-
-TOOL_MODE=diff
-. "$(git --exec-path)/git-mergetool--lib"
-
-merge_tool="$(get_merge_tool)"
-if test -z "$merge_tool"
-then
- echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
- usage
-fi
-
-start_dir=$(pwd)
-
-# All the file paths returned by the diff command are relative to the root
-# of the working copy. So if the script is called from a subdirectory, it
-# must switch to the root of working copy before trying to use those paths.
-cdup=$(git rev-parse --show-cdup) &&
-cd "$cdup" || {
- echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
- exit 1
-}
-
-# set up temp dir
-tmp=$(perl -e 'use File::Temp qw(tempdir);
- $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1);
- print $t') || exit 1
-trap 'rm -rf "$tmp"' EXIT
-
-left=
-right=
-paths=
-dashdash_seen=
-compare_staged=
-merge_base=
-left_dir=
-right_dir=
-diff_tool=
-copy_back=
-
-while test $# != 0
-do
- case "$1" in
- -h|--h|--he|--hel|--help)
- usage
- ;;
- --cached)
- compare_staged=1
- ;;
- --copy-back)
- copy_back=1
- ;;
- -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
- if test $# = 1
- then
- echo You must specify the tool for use with --extcmd
- usage
- else
- diff_tool=$2
- shift
- fi
- ;;
- --)
- dashdash_seen=1
- ;;
- -*)
- echo Invalid option: "$1"
- usage
- ;;
- *)
- # could be commit, commit range or path limiter
- case "$1" in
- *...*)
- left=${1%...*}
- right=${1#*...}
- merge_base=1
- ;;
- *..*)
- left=${1%..*}
- right=${1#*..}
- ;;
- *)
- if test -n "$dashdash_seen"
- then
- paths="$paths$1 "
- elif test -z "$left"
- then
- left=$1
- elif test -z "$right"
- then
- right=$1
- else
- paths="$paths$1 "
- fi
- ;;
- esac
- ;;
- esac
- shift
-done
-
-# Determine the set of files which changed
-if test -n "$left" && test -n "$right"
-then
- left_dir="cmt-$(git rev-parse --short $left)"
- right_dir="cmt-$(git rev-parse --short $right)"
-
- if test -n "$compare_staged"
- then
- usage
- elif test -n "$merge_base"
- then
- git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
- else
- git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
- fi
-elif test -n "$left"
-then
- left_dir="cmt-$(git rev-parse --short $left)"
-
- if test -n "$compare_staged"
- then
- right_dir="staged"
- git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
- else
- right_dir="working_tree"
- git diff --name-only "$left" -- $paths >"$tmp/filelist"
- fi
-else
- left_dir="HEAD"
-
- if test -n "$compare_staged"
- then
- right_dir="staged"
- git diff --name-only --cached -- $paths >"$tmp/filelist"
- else
- right_dir="working_tree"
- git diff --name-only -- $paths >"$tmp/filelist"
- fi
-fi
-
-# Exit immediately if there are no diffs
-if test ! -s "$tmp/filelist"
-then
- exit 0
-fi
-
-if test -n "$copy_back" && test "$right_dir" != "working_tree"
-then
- echo "--copy-back is only valid when diff includes the working tree."
- exit 1
-fi
-
-# Create the named tmp directories that will hold the files to be compared
-mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
-
-# Populate the tmp/right_dir directory with the files to be compared
-while read name
-do
- if test -n "$right"
- then
- ls_list=$(git ls-tree $right "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- git show "$right":"$name" >"$tmp/$right_dir/$name" || true
- fi
- elif test -n "$compare_staged"
- then
- ls_list=$(git ls-files -- "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- git show :"$name" >"$tmp/$right_dir/$name"
- fi
- else
- if test -e "$name"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- cp "$name" "$tmp/$right_dir/$name"
- fi
- fi
-done < "$tmp/filelist"
-
-# Populate the tmp/left_dir directory with the files to be compared
-while read name
-do
- if test -n "$left"
- then
- ls_list=$(git ls-tree $left "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show "$left":"$name" >"$tmp/$left_dir/$name" || true
- fi
- else
- if test -n "$compare_staged"
- then
- ls_list=$(git ls-tree HEAD "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show HEAD:"$name" >"$tmp/$left_dir/$name"
- fi
- else
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show :"$name" >"$tmp/$left_dir/$name"
- fi
- fi
-done < "$tmp/filelist"
-
-LOCAL="$tmp/$left_dir"
-REMOTE="$tmp/$right_dir"
-
-if test -n "$diff_tool"
-then
- export BASE
- eval $diff_tool '"$LOCAL"' '"$REMOTE"'
-else
- run_merge_tool "$merge_tool" false
-fi
-
-# Copy files back to the working dir, if requested
-if test -n "$copy_back" && test "$right_dir" = "working_tree"
-then
- cd "$start_dir"
- git_top_dir=$(git rev-parse --show-toplevel)
- find "$tmp/$right_dir" -type f |
- while read file
- do
- cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
- done
-fi
rla = "(reflog update)";
if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
warning("reflog message too long: %.*s...", 50, msg);
- return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
+ return update_ref(msg, refname, sha1, oldval, 0,
+ UPDATE_REFS_QUIET_ON_ERR);
}
static int update_local_ref(const char *name,
my $response = $mediawiki->{ua}->get($download_url);
if ($response->code == HTTP_CODE_OK) {
- return $response->decoded_content;
+ # It is tempting to return
+ # $response->decoded_content({charset => "none"}), but
+ # when doing so, utf8::downgrade($content) fails with
+ # "Wide character in subroutine entry".
+ $response->decode();
+ return $response->content();
} else {
print {*STDERR} "Error downloading mediafile from :\n";
print {*STDERR} "URL: ${download_url}\n";
echo " install | -i : Install a wiki on your computer."
echo " delete | -d : Delete the wiki and all its pages and "
echo " content."
+ echo " start | -s : Start the previously configured lighttpd daemon"
+ echo " stop : Stop lighttpd daemon."
}
wiki_delete
exit 0
;;
+ "start" | "-s")
+ start_lighttpd
+ exit
+ ;;
+ "stop")
+ stop_lighttpd
+ exit
+ ;;
"--help" | "-h")
usage
exit 0
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt
'
+test_expect_success 'git push can upload media (File:) files containing valid UTF-8' '
+ wiki_reset &&
+ git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+ (
+ cd mw_dir &&
+ "$PERL_PATH" -e "print STDOUT \"UTF-8 content: éèàéê€.\";" >Bar.txt &&
+ git add Bar.txt &&
+ git commit -m "add a text file with UTF-8 content" &&
+ git push
+ )
+'
+
+test_expect_success 'git clone works on previously created wiki with media files containing valid UTF-8' '
+ test_when_finished "rm -rf mw_dir mw_dir_clone" &&
+ git clone -c remote.origin.mediaimport=true \
+ mediawiki::'"$WIKI_URL"' mw_dir_clone &&
+ test_cmp mw_dir_clone/Bar.txt mw_dir/Bar.txt
+'
+
test_expect_success 'git push & pull work with locally renamed media files' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir &&
test_expect_success 'creating page w/ >500 revisions' '
wiki_reset &&
- for i in `test_seq 501`
+ for i in $(test_seq 501)
do
echo "creating revision $i" &&
wiki_editpage foo "revision $i<br/>" true
#
# Check that <dir> contains exactly <N> files
test_contains_N_files () {
- if test `ls -- "$1" | wc -l` -ne "$2"; then
+ if test $(ls -- "$1" | wc -l) -ne "$2"; then
echo "directory $1 should contain $2 files"
echo "it contains these files:"
ls "$1"
# Kill daemon lighttpd and removes files and folders associated.
stop_lighttpd () {
test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid")
- rm -rf "$WEB"
}
# Create the SQLite database of the MediaWiki. If the database file already
"http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
"$MW_FILENAME. "\
"Please fix your connection and launch the script again."
- echo "$MW_FILENAME downloaded in `pwd`. "\
+ echo "$MW_FILENAME downloaded in $(pwd). "\
"You can delete it later if you want."
else
- echo "Reusing existing $MW_FILENAME downloaded in `pwd`."
+ echo "Reusing existing $MW_FILENAME downloaded in $(pwd)."
fi
archive_abs_path=$(pwd)/$MW_FILENAME
cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
wiki_delete () {
if test $LIGHTTPD = "true"; then
stop_lighttpd
+ rm -fr "$WEB"
else
# Delete the wiki's directory.
rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" ||
+++ /dev/null
-TESTS := $(wildcard test*.sh)
-
-export T := $(addprefix $(CURDIR)/,$(TESTS))
-export MAKE := $(MAKE) -e
-export PATH := $(CURDIR):$(PATH)
-export TEST_LINT := test-lint-executable test-lint-shell-syntax
-
-test:
- $(MAKE) -C ../../t $@
-
-$(TESTS):
- $(MAKE) -C ../../t $(CURDIR)/$@
-
-.PHONY: $(TESTS)
--- /dev/null
+The remote-helper bridges to access data stored in Mercurial and
+Bazaar are maintained outside the git.git tree in the repositories
+of their primary author:
+
+ https://github.com/felipec/git-remote-hg (for Mercurial)
+ https://github.com/felipec/git-remote-bzr (for Bazaar)
+
+You can pick a directory on your $PATH and download them from these
+repositories, e.g.:
+
+ $ wget -O $HOME/bin/git-remote-hg \
+ https://raw.github.com/felipec/git-remote-hg/master/git-remote-hg
+ $ wget -O $HOME/bin/git-remote-bzr \
+ https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr
+ $ chmod +x $HOME/bin/git-remote-hg $HOME/bin/git-remote-bzr
#!/usr/bin/env python
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-
-#
-# Just copy to your ~/bin, or anywhere in your $PATH.
-# Then you can clone with:
-# % git clone bzr::/path/to/bzr/repo/or/url
-#
-# For example:
-# % git clone bzr::$HOME/myrepo
-# or
-# % git clone bzr::lp:myrepo
-#
-# If you want to specify which branches you want to track (per repo):
-# % git config remote.origin.bzr-branches 'trunk, devel, test'
-#
-# Where 'origin' is the name of the repository you want to specify the
-# branches.
-#
-
-import sys
-
-import bzrlib
-if hasattr(bzrlib, "initialize"):
- bzrlib.initialize()
-
-import bzrlib.plugin
-bzrlib.plugin.load_plugins()
-
-import bzrlib.generate_ids
-import bzrlib.transport
-import bzrlib.errors
-import bzrlib.ui
-import bzrlib.urlutils
-import bzrlib.branch
import sys
-import os
-import json
-import re
-import StringIO
-import atexit, shutil, hashlib, urlparse, subprocess
-
-NAME_RE = re.compile('^([^<>]+)')
-AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
-EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
-RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
-
-def die(msg, *args):
- sys.stderr.write('ERROR: %s\n' % (msg % args))
- sys.exit(1)
-
-def warn(msg, *args):
- sys.stderr.write('WARNING: %s\n' % (msg % args))
-
-def gittz(tz):
- return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
-
-def get_config(config):
- cmd = ['git', 'config', '--get', config]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- output, _ = process.communicate()
- return output
-
-class Marks:
-
- def __init__(self, path):
- self.path = path
- self.tips = {}
- self.marks = {}
- self.rev_marks = {}
- self.last_mark = 0
- self.load()
-
- def load(self):
- if not os.path.exists(self.path):
- return
-
- tmp = json.load(open(self.path))
- self.tips = tmp['tips']
- self.marks = tmp['marks']
- self.last_mark = tmp['last-mark']
-
- for rev, mark in self.marks.iteritems():
- self.rev_marks[mark] = rev
-
- def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
-
- def store(self):
- json.dump(self.dict(), open(self.path, 'w'))
-
- def __str__(self):
- return str(self.dict())
-
- def from_rev(self, rev):
- return self.marks[rev]
-
- def to_rev(self, mark):
- return str(self.rev_marks[mark])
-
- def next_mark(self):
- self.last_mark += 1
- return self.last_mark
-
- def get_mark(self, rev):
- self.last_mark += 1
- self.marks[rev] = self.last_mark
- return self.last_mark
-
- def is_marked(self, rev):
- return rev in self.marks
-
- def new_mark(self, rev, mark):
- self.marks[rev] = mark
- self.rev_marks[mark] = rev
- self.last_mark = mark
-
- def get_tip(self, branch):
- try:
- return str(self.tips[branch])
- except KeyError:
- return None
-
- def set_tip(self, branch, tip):
- self.tips[branch] = tip
-
-class Parser:
-
- def __init__(self, repo):
- self.repo = repo
- self.line = self.get_line()
-
- def get_line(self):
- return sys.stdin.readline().strip()
-
- def __getitem__(self, i):
- return self.line.split()[i]
-
- def check(self, word):
- return self.line.startswith(word)
-
- def each_block(self, separator):
- while self.line != separator:
- yield self.line
- self.line = self.get_line()
-
- def __iter__(self):
- return self.each_block('')
-
- def next(self):
- self.line = self.get_line()
- if self.line == 'done':
- self.line = None
-
- def get_mark(self):
- i = self.line.index(':') + 1
- return int(self.line[i:])
-
- def get_data(self):
- if not self.check('data'):
- return None
- i = self.line.index(' ') + 1
- size = int(self.line[i:])
- return sys.stdin.read(size)
-
- def get_author(self):
- m = RAW_AUTHOR_RE.match(self.line)
- if not m:
- return None
- _, name, email, date, tz = m.groups()
- name = name.decode('utf-8')
- committer = '%s <%s>' % (name, email)
- tz = int(tz)
- tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
- return (committer, int(date), tz)
-
-def rev_to_mark(rev):
- return marks.from_rev(rev)
-
-def mark_to_rev(mark):
- return marks.to_rev(mark)
-
-def fixup_user(user):
- name = mail = None
- user = user.replace('"', '')
- m = AUTHOR_RE.match(user)
- if m:
- name = m.group(1)
- mail = m.group(2).strip()
- else:
- m = EMAIL_RE.match(user)
- if m:
- mail = m.group(1)
- else:
- m = NAME_RE.match(user)
- if m:
- name = m.group(1).strip()
-
- if not name:
- name = 'unknown'
- if not mail:
- mail = 'Unknown'
-
- return '%s <%s>' % (name, mail)
-
-def get_filechanges(cur, prev):
- modified = {}
- removed = {}
-
- changes = cur.changes_from(prev)
-
- def u(s):
- return s.encode('utf-8')
-
- for path, fid, kind in changes.added:
- modified[u(path)] = fid
- for path, fid, kind in changes.removed:
- removed[u(path)] = None
- for path, fid, kind, mod, _ in changes.modified:
- modified[u(path)] = fid
- for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
- removed[u(oldpath)] = None
- if kind == 'directory':
- lst = cur.list_files(from_dir=newpath, recursive=True)
- for path, file_class, kind, fid, entry in lst:
- if kind != 'directory':
- modified[u(newpath + '/' + path)] = fid
- else:
- modified[u(newpath)] = fid
-
- return modified, removed
-
-def export_files(tree, files):
- final = []
- for path, fid in files.iteritems():
- kind = tree.kind(fid)
-
- h = tree.get_file_sha1(fid)
-
- if kind == 'symlink':
- d = tree.get_symlink_target(fid)
- mode = '120000'
- elif kind == 'file':
-
- if tree.is_executable(fid):
- mode = '100755'
- else:
- mode = '100644'
-
- # is the blob already exported?
- if h in filenodes:
- mark = filenodes[h]
- final.append((mode, mark, path))
- continue
-
- d = tree.get_file_text(fid)
- elif kind == 'directory':
- continue
- else:
- die("Unhandled kind '%s' for path '%s'" % (kind, path))
-
- mark = marks.next_mark()
- filenodes[h] = mark
-
- print "blob"
- print "mark :%u" % mark
- print "data %d" % len(d)
- print d
-
- final.append((mode, mark, path))
-
- return final
-
-def export_branch(repo, name):
- ref = '%s/heads/%s' % (prefix, name)
- tip = marks.get_tip(name)
-
- branch = get_remote_branch(name)
- repo = branch.repository
-
- branch.lock_read()
- revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
- try:
- tip_revno = branch.revision_id_to_revno(tip)
- last_revno, _ = branch.last_revision_info()
- total = last_revno - tip_revno
- except bzrlib.errors.NoSuchRevision:
- tip_revno = 0
- total = 0
-
- for revid, _, seq, _ in revs:
-
- if marks.is_marked(revid):
- continue
-
- rev = repo.get_revision(revid)
- revno = seq[0]
-
- parents = rev.parent_ids
- time = rev.timestamp
- tz = rev.timezone
- committer = rev.committer.encode('utf-8')
- committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
- authors = rev.get_apparent_authors()
- if authors:
- author = authors[0].encode('utf-8')
- author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
- else:
- author = committer
- msg = rev.message.encode('utf-8')
-
- msg += '\n'
-
- if len(parents) == 0:
- parent = bzrlib.revision.NULL_REVISION
- else:
- parent = parents[0]
-
- cur_tree = repo.revision_tree(revid)
- prev = repo.revision_tree(parent)
- modified, removed = get_filechanges(cur_tree, prev)
-
- modified_final = export_files(cur_tree, modified)
-
- if len(parents) == 0:
- print 'reset %s' % ref
-
- print "commit %s" % ref
- print "mark :%d" % (marks.get_mark(revid))
- print "author %s" % (author)
- print "committer %s" % (committer)
- print "data %d" % (len(msg))
- print msg
-
- for i, p in enumerate(parents):
- try:
- m = rev_to_mark(p)
- except KeyError:
- # ghost?
- continue
- if i == 0:
- print "from :%s" % m
- else:
- print "merge :%s" % m
-
- for f in removed:
- print "D %s" % (f,)
- for f in modified_final:
- print "M %s :%u %s" % f
- print
-
- if len(seq) > 1:
- # let's skip branch revisions from the progress report
- continue
-
- progress = (revno - tip_revno)
- if (progress % 100 == 0):
- if total:
- print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
- else:
- print "progress revision %d '%s' (%d)" % (revno, name, progress)
-
- branch.unlock()
-
- revid = branch.last_revision()
-
- # make sure the ref is updated
- print "reset %s" % ref
- print "from :%u" % rev_to_mark(revid)
- print
-
- marks.set_tip(name, revid)
-
-def export_tag(repo, name):
- ref = '%s/tags/%s' % (prefix, name)
- print "reset %s" % ref
- print "from :%u" % rev_to_mark(tags[name])
- print
-
-def do_import(parser):
- repo = parser.repo
- path = os.path.join(dirname, 'marks-git')
-
- print "feature done"
- if os.path.exists(path):
- print "feature import-marks=%s" % path
- print "feature export-marks=%s" % path
- print "feature force"
- sys.stdout.flush()
-
- while parser.check('import'):
- ref = parser[1]
- if ref.startswith('refs/heads/'):
- name = ref[len('refs/heads/'):]
- export_branch(repo, name)
- if ref.startswith('refs/tags/'):
- name = ref[len('refs/tags/'):]
- export_tag(repo, name)
- parser.next()
-
- print 'done'
-
- sys.stdout.flush()
-
-def parse_blob(parser):
- parser.next()
- mark = parser.get_mark()
- parser.next()
- data = parser.get_data()
- blob_marks[mark] = data
- parser.next()
-
-class CustomTree():
-
- def __init__(self, branch, revid, parents, files):
- self.updates = {}
- self.branch = branch
-
- def copy_tree(revid):
- files = files_cache[revid] = {}
- branch.lock_read()
- tree = branch.repository.revision_tree(revid)
- try:
- for path, entry in tree.iter_entries_by_dir():
- files[path] = [entry.file_id, None]
- finally:
- branch.unlock()
- return files
-
- if len(parents) == 0:
- self.base_id = bzrlib.revision.NULL_REVISION
- self.base_files = {}
- else:
- self.base_id = parents[0]
- self.base_files = files_cache.get(self.base_id, None)
- if not self.base_files:
- self.base_files = copy_tree(self.base_id)
-
- self.files = files_cache[revid] = self.base_files.copy()
- self.rev_files = {}
-
- for path, data in self.files.iteritems():
- fid, mark = data
- self.rev_files[fid] = [path, mark]
-
- for path, f in files.iteritems():
- fid, mark = self.files.get(path, [None, None])
- if not fid:
- fid = bzrlib.generate_ids.gen_file_id(path)
- f['path'] = path
- self.rev_files[fid] = [path, mark]
- self.updates[fid] = f
-
- def last_revision(self):
- return self.base_id
-
- def iter_changes(self):
- changes = []
-
- def get_parent(dirname, basename):
- parent_fid, mark = self.base_files.get(dirname, [None, None])
- if parent_fid:
- return parent_fid
- parent_fid, mark = self.files.get(dirname, [None, None])
- if parent_fid:
- return parent_fid
- if basename == '':
- return None
- fid = bzrlib.generate_ids.gen_file_id(path)
- add_entry(fid, dirname, 'directory')
- return fid
-
- def add_entry(fid, path, kind, mode=None):
- dirname, basename = os.path.split(path)
- parent_fid = get_parent(dirname, basename)
-
- executable = False
- if mode == '100755':
- executable = True
- elif mode == '120000':
- kind = 'symlink'
-
- change = (fid,
- (None, path),
- True,
- (False, True),
- (None, parent_fid),
- (None, basename),
- (None, kind),
- (None, executable))
- self.files[path] = [change[0], None]
- changes.append(change)
-
- def update_entry(fid, path, kind, mode=None):
- dirname, basename = os.path.split(path)
- parent_fid = get_parent(dirname, basename)
-
- executable = False
- if mode == '100755':
- executable = True
- elif mode == '120000':
- kind = 'symlink'
-
- change = (fid,
- (path, path),
- True,
- (True, True),
- (None, parent_fid),
- (None, basename),
- (None, kind),
- (None, executable))
- self.files[path] = [change[0], None]
- changes.append(change)
-
- def remove_entry(fid, path, kind):
- dirname, basename = os.path.split(path)
- parent_fid = get_parent(dirname, basename)
- change = (fid,
- (path, None),
- True,
- (True, False),
- (parent_fid, None),
- (None, None),
- (None, None),
- (None, None))
- del self.files[path]
- changes.append(change)
-
- for fid, f in self.updates.iteritems():
- path = f['path']
-
- if 'deleted' in f:
- remove_entry(fid, path, 'file')
- continue
-
- if path in self.base_files:
- update_entry(fid, path, 'file', f['mode'])
- else:
- add_entry(fid, path, 'file', f['mode'])
-
- self.files[path][1] = f['mark']
- self.rev_files[fid][1] = f['mark']
-
- return changes
-
- def get_content(self, file_id):
- path, mark = self.rev_files[file_id]
- if mark:
- return blob_marks[mark]
-
- # last resort
- tree = self.branch.repository.revision_tree(self.base_id)
- return tree.get_file_text(file_id)
-
- def get_file_with_stat(self, file_id, path=None):
- content = self.get_content(file_id)
- return (StringIO.StringIO(content), None)
-
- def get_symlink_target(self, file_id):
- return self.get_content(file_id)
-
- def id2path(self, file_id):
- path, mark = self.rev_files[file_id]
- return path
-
-def c_style_unescape(string):
- if string[0] == string[-1] == '"':
- return string.decode('string-escape')[1:-1]
- return string
-
-def parse_commit(parser):
- parents = []
-
- ref = parser[1]
- parser.next()
-
- if ref.startswith('refs/heads/'):
- name = ref[len('refs/heads/'):]
- branch = get_remote_branch(name)
- else:
- die('unknown ref')
-
- commit_mark = parser.get_mark()
- parser.next()
- author = parser.get_author()
- parser.next()
- committer = parser.get_author()
- parser.next()
- data = parser.get_data()
- parser.next()
- if parser.check('from'):
- parents.append(parser.get_mark())
- parser.next()
- while parser.check('merge'):
- parents.append(parser.get_mark())
- parser.next()
-
- # fast-export adds an extra newline
- if data[-1] == '\n':
- data = data[:-1]
-
- files = {}
-
- for line in parser:
- if parser.check('M'):
- t, m, mark_ref, path = line.split(' ', 3)
- mark = int(mark_ref[1:])
- f = { 'mode' : m, 'mark' : mark }
- elif parser.check('D'):
- t, path = line.split(' ', 1)
- f = { 'deleted' : True }
- else:
- die('Unknown file command: %s' % line)
- path = c_style_unescape(path).decode('utf-8')
- files[path] = f
-
- committer, date, tz = committer
- author, _, _ = author
- parents = [mark_to_rev(p) for p in parents]
- revid = bzrlib.generate_ids.gen_revision_id(committer, date)
- props = {}
- props['branch-nick'] = branch.nick
- props['authors'] = author
-
- mtree = CustomTree(branch, revid, parents, files)
- changes = mtree.iter_changes()
-
- branch.lock_write()
- try:
- builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
- try:
- list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
- builder.finish_inventory()
- builder.commit(data.decode('utf-8', 'replace'))
- except Exception, e:
- builder.abort()
- raise
- finally:
- branch.unlock()
-
- parsed_refs[ref] = revid
- marks.new_mark(revid, commit_mark)
-
-def parse_reset(parser):
- ref = parser[1]
- parser.next()
-
- # ugh
- if parser.check('commit'):
- parse_commit(parser)
- return
- if not parser.check('from'):
- return
- from_mark = parser.get_mark()
- parser.next()
-
- parsed_refs[ref] = mark_to_rev(from_mark)
-
-def do_export(parser):
- parser.next()
-
- for line in parser.each_block('done'):
- if parser.check('blob'):
- parse_blob(parser)
- elif parser.check('commit'):
- parse_commit(parser)
- elif parser.check('reset'):
- parse_reset(parser)
- elif parser.check('tag'):
- pass
- elif parser.check('feature'):
- pass
- else:
- die('unhandled export command: %s' % line)
-
- for ref, revid in parsed_refs.iteritems():
- if ref.startswith('refs/heads/'):
- name = ref[len('refs/heads/'):]
- branch = get_remote_branch(name)
- branch.generate_revision_history(revid, marks.get_tip(name))
-
- if name in peers:
- peer = bzrlib.branch.Branch.open(peers[name],
- possible_transports=transports)
- try:
- peer.bzrdir.push_branch(branch, revision_id=revid,
- overwrite=force)
- except bzrlib.errors.DivergedBranches:
- print "error %s non-fast forward" % ref
- continue
-
- try:
- wt = branch.bzrdir.open_workingtree()
- wt.update()
- except bzrlib.errors.NoWorkingTree:
- pass
- elif ref.startswith('refs/tags/'):
- # TODO: implement tag push
- print "error %s pushing tags not supported" % ref
- continue
- else:
- # transport-helper/fast-export bugs
- continue
-
- print "ok %s" % ref
-
- print
-
-def do_capabilities(parser):
- print "import"
- print "export"
- print "refspec refs/heads/*:%s/heads/*" % prefix
- print "refspec refs/tags/*:%s/tags/*" % prefix
-
- path = os.path.join(dirname, 'marks-git')
-
- if os.path.exists(path):
- print "*import-marks %s" % path
- print "*export-marks %s" % path
-
- print "option"
- print
-
-class InvalidOptionValue(Exception):
- pass
-
-def get_bool_option(val):
- if val == 'true':
- return True
- elif val == 'false':
- return False
- else:
- raise InvalidOptionValue()
-
-def do_option(parser):
- global force
- opt, val = parser[1:3]
- try:
- if opt == 'force':
- force = get_bool_option(val)
- print 'ok'
- else:
- print 'unsupported'
- except InvalidOptionValue:
- print "error '%s' is not a valid value for option '%s'" % (val, opt)
-
-def ref_is_valid(name):
- return not True in [c in name for c in '~^: \\']
-
-def do_list(parser):
- master_branch = None
-
- for name in branches:
- if not master_branch:
- master_branch = name
- print "? refs/heads/%s" % name
-
- branch = get_remote_branch(master_branch)
- branch.lock_read()
- for tag, revid in branch.tags.get_tag_dict().items():
- try:
- branch.revision_id_to_dotted_revno(revid)
- except bzrlib.errors.NoSuchRevision:
- continue
- if not ref_is_valid(tag):
- continue
- print "? refs/tags/%s" % tag
- tags[tag] = revid
- branch.unlock()
-
- print "@refs/heads/%s HEAD" % master_branch
- print
-
-def clone(path, remote_branch):
- try:
- bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
- except bzrlib.errors.AlreadyControlDirError:
- bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
- repo = bdir.find_repository()
- repo.fetch(remote_branch.repository)
- return remote_branch.sprout(bdir, repository=repo)
-
-def get_remote_branch(name):
- remote_branch = bzrlib.branch.Branch.open(branches[name],
- possible_transports=transports)
- if isinstance(remote_branch.bzrdir.root_transport, bzrlib.transport.local.LocalTransport):
- return remote_branch
-
- branch_path = os.path.join(dirname, 'clone', name)
-
- try:
- branch = bzrlib.branch.Branch.open(branch_path,
- possible_transports=transports)
- except bzrlib.errors.NotBranchError:
- # clone
- branch = clone(branch_path, remote_branch)
- else:
- # pull
- try:
- branch.pull(remote_branch, overwrite=True)
- except bzrlib.errors.DivergedBranches:
- # use remote branch for now
- return remote_branch
-
- return branch
-
-def find_branches(repo):
- transport = repo.bzrdir.root_transport
-
- for fn in transport.iter_files_recursive():
- if not fn.endswith('.bzr/branch-format'):
- continue
-
- name = subdir = fn[:-len('/.bzr/branch-format')]
- name = name if name != '' else 'master'
- name = name.replace('/', '+')
-
- try:
- cur = transport.clone(subdir)
- branch = bzrlib.branch.Branch.open_from_transport(cur)
- except bzrlib.errors.NotBranchError:
- continue
- else:
- yield name, branch.base
-
-def get_repo(url, alias):
- normal_url = bzrlib.urlutils.normalize_url(url)
- origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
- is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
-
- shared_path = os.path.join(gitdir, 'bzr')
- try:
- shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
- possible_transports=transports)
- except bzrlib.errors.NotBranchError:
- shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
- possible_transports=transports)
- try:
- shared_repo = shared_dir.open_repository()
- except bzrlib.errors.NoRepositoryPresent:
- shared_repo = shared_dir.create_repository(shared=True)
-
- if not is_local:
- clone_path = os.path.join(dirname, 'clone')
- if not os.path.exists(clone_path):
- os.mkdir(clone_path)
- else:
- # check and remove old organization
- try:
- bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
- possible_transports=transports)
- bdir.destroy_repository()
- except bzrlib.errors.NotBranchError:
- pass
- except bzrlib.errors.NoRepositoryPresent:
- pass
-
- wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
- # stupid python
- wanted = [e for e in wanted if e]
- if not wanted:
- wanted = get_config('remote-bzr.branches').rstrip().split(', ')
- # stupid python
- wanted = [e for e in wanted if e]
-
- if not wanted:
- try:
- repo = origin.open_repository()
- if not repo.bzrdir.root_transport.listable():
- # this repository is not usable for us
- raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
- except bzrlib.errors.NoRepositoryPresent:
- wanted = ['master']
-
- if wanted:
- def list_wanted(url, wanted):
- for name in wanted:
- subdir = name if name != 'master' else ''
- yield name, bzrlib.urlutils.join(url, subdir)
-
- branch_list = list_wanted(url, wanted)
- else:
- branch_list = find_branches(repo)
-
- for name, url in branch_list:
- if not is_local:
- peers[name] = url
- branches[name] = url
-
- return origin
-
-def fix_path(alias, orig_url):
- url = urlparse.urlparse(orig_url, 'file')
- if url.scheme != 'file' or os.path.isabs(url.path):
- return
- abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
- cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
- subprocess.call(cmd)
-
-def main(args):
- global marks, prefix, gitdir, dirname
- global tags, filenodes
- global blob_marks
- global parsed_refs
- global files_cache
- global is_tmp
- global branches, peers
- global transports
- global force
-
- marks = None
- is_tmp = False
- gitdir = os.environ.get('GIT_DIR', None)
-
- if len(args) < 3:
- die('Not enough arguments.')
-
- if not gitdir:
- die('GIT_DIR not set')
-
- alias = args[1]
- url = args[2]
-
- tags = {}
- filenodes = {}
- blob_marks = {}
- parsed_refs = {}
- files_cache = {}
- branches = {}
- peers = {}
- transports = []
- force = False
-
- if alias[5:] == url:
- is_tmp = True
- alias = hashlib.sha1(alias).hexdigest()
-
- prefix = 'refs/bzr/%s' % alias
- dirname = os.path.join(gitdir, 'bzr', alias)
-
- if not is_tmp:
- fix_path(alias, url)
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
- bzrlib.ui.ui_factory.be_quiet(True)
-
- repo = get_repo(url, alias)
-
- marks_path = os.path.join(dirname, 'marks-int')
- marks = Marks(marks_path)
-
- parser = Parser(repo)
- for line in parser:
- if parser.check('capabilities'):
- do_capabilities(parser)
- elif parser.check('list'):
- do_list(parser)
- elif parser.check('import'):
- do_import(parser)
- elif parser.check('export'):
- do_export(parser)
- elif parser.check('option'):
- do_option(parser)
- else:
- die('unhandled command: %s' % line)
- sys.stdout.flush()
-def bye():
- if not marks:
- return
- if not is_tmp:
- marks.store()
- else:
- shutil.rmtree(dirname)
+sys.stderr.write('WARNING: git-remote-bzr is now maintained independently.\n')
+sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-bzr\n')
-atexit.register(bye)
-sys.exit(main(sys.argv))
+sys.stderr.write('''WARNING:
+WARNING: You can pick a directory on your $PATH and download it, e.g.:
+WARNING: $ wget -O $HOME/bin/git-remote-bzr \\
+WARNING: https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr
+WARNING: $ chmod +x $HOME/bin/git-remote-bzr
+''')
#!/usr/bin/env python
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Inspired by Rocco Rutte's hg-fast-export
-
-# Just copy to your ~/bin, or anywhere in your $PATH.
-# Then you can clone with:
-# git clone hg::/path/to/mercurial/repo/
-#
-# For remote repositories a local clone is stored in
-# "$GIT_DIR/hg/origin/clone/.hg/".
-
-from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
-
-import re
import sys
-import os
-import json
-import shutil
-import subprocess
-import urllib
-import atexit
-import urlparse, hashlib
-import time as ptime
-
-#
-# If you want to see Mercurial revisions as Git commit notes:
-# git config core.notesRef refs/notes/hg
-#
-# If you are not in hg-git-compat mode and want to disable the tracking of
-# named branches:
-# git config --global remote-hg.track-branches false
-#
-# If you want the equivalent of hg's clone/pull--insecure option:
-# git config --global remote-hg.insecure true
-#
-# If you want to switch to hg-git compatibility mode:
-# git config --global remote-hg.hg-git-compat true
-#
-# git:
-# Sensible defaults for git.
-# hg bookmarks are exported as git branches, hg branches are prefixed
-# with 'branches/', HEAD is a special case.
-#
-# hg:
-# Emulate hg-git.
-# Only hg bookmarks are exported as git branches.
-# Commits are modified to preserve hg information and allow bidirectionality.
-#
-
-NAME_RE = re.compile('^([^<>]+)')
-AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
-EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
-AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
-RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
-
-VERSION = 2
-
-def die(msg, *args):
- sys.stderr.write('ERROR: %s\n' % (msg % args))
- sys.exit(1)
-
-def warn(msg, *args):
- sys.stderr.write('WARNING: %s\n' % (msg % args))
-
-def gitmode(flags):
- return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
-
-def gittz(tz):
- return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
-
-def hgmode(mode):
- m = { '100755': 'x', '120000': 'l' }
- return m.get(mode, '')
-
-def hghex(n):
- return node.hex(n)
-
-def hgbin(n):
- return node.bin(n)
-
-def hgref(ref):
- return ref.replace('___', ' ')
-
-def gitref(ref):
- return ref.replace(' ', '___')
-
-def check_version(*check):
- if not hg_version:
- return True
- return hg_version >= check
-
-def get_config(config):
- cmd = ['git', 'config', '--get', config]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- output, _ = process.communicate()
- return output
-
-def get_config_bool(config, default=False):
- value = get_config(config).rstrip('\n')
- if value == "true":
- return True
- elif value == "false":
- return False
- else:
- return default
-
-class Marks:
-
- def __init__(self, path, repo):
- self.path = path
- self.repo = repo
- self.clear()
- self.load()
-
- if self.version < VERSION:
- if self.version == 1:
- self.upgrade_one()
-
- # upgraded?
- if self.version < VERSION:
- self.clear()
- self.version = VERSION
-
- def clear(self):
- self.tips = {}
- self.marks = {}
- self.rev_marks = {}
- self.last_mark = 0
- self.version = 0
- self.last_note = 0
-
- def load(self):
- if not os.path.exists(self.path):
- return
-
- tmp = json.load(open(self.path))
-
- self.tips = tmp['tips']
- self.marks = tmp['marks']
- self.last_mark = tmp['last-mark']
- self.version = tmp.get('version', 1)
- self.last_note = tmp.get('last-note', 0)
-
- for rev, mark in self.marks.iteritems():
- self.rev_marks[mark] = rev
-
- def upgrade_one(self):
- def get_id(rev):
- return hghex(self.repo.changelog.node(int(rev)))
- self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
- self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
- self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
- self.version = 2
-
- def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
-
- def store(self):
- json.dump(self.dict(), open(self.path, 'w'))
-
- def __str__(self):
- return str(self.dict())
-
- def from_rev(self, rev):
- return self.marks[rev]
-
- def to_rev(self, mark):
- return str(self.rev_marks[mark])
-
- def next_mark(self):
- self.last_mark += 1
- return self.last_mark
-
- def get_mark(self, rev):
- self.last_mark += 1
- self.marks[rev] = self.last_mark
- return self.last_mark
-
- def new_mark(self, rev, mark):
- self.marks[rev] = mark
- self.rev_marks[mark] = rev
- self.last_mark = mark
-
- def is_marked(self, rev):
- return rev in self.marks
-
- def get_tip(self, branch):
- return str(self.tips[branch])
-
- def set_tip(self, branch, tip):
- self.tips[branch] = tip
-
-class Parser:
-
- def __init__(self, repo):
- self.repo = repo
- self.line = self.get_line()
-
- def get_line(self):
- return sys.stdin.readline().strip()
-
- def __getitem__(self, i):
- return self.line.split()[i]
-
- def check(self, word):
- return self.line.startswith(word)
-
- def each_block(self, separator):
- while self.line != separator:
- yield self.line
- self.line = self.get_line()
-
- def __iter__(self):
- return self.each_block('')
-
- def next(self):
- self.line = self.get_line()
- if self.line == 'done':
- self.line = None
-
- def get_mark(self):
- i = self.line.index(':') + 1
- return int(self.line[i:])
-
- def get_data(self):
- if not self.check('data'):
- return None
- i = self.line.index(' ') + 1
- size = int(self.line[i:])
- return sys.stdin.read(size)
-
- def get_author(self):
- ex = None
- m = RAW_AUTHOR_RE.match(self.line)
- if not m:
- return None
- _, name, email, date, tz = m.groups()
- if name and 'ext:' in name:
- m = re.match('^(.+?) ext:\((.+)\)$', name)
- if m:
- name = m.group(1)
- ex = urllib.unquote(m.group(2))
-
- if email != bad_mail:
- if name:
- user = '%s <%s>' % (name, email)
- else:
- user = '<%s>' % (email)
- else:
- user = name
-
- if ex:
- user += ex
-
- tz = int(tz)
- tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
- return (user, int(date), -tz)
-
-def fix_file_path(path):
- path = os.path.normpath(path)
- if not os.path.isabs(path):
- return path
- return os.path.relpath(path, '/')
-
-def export_files(files):
- final = []
- for f in files:
- fid = node.hex(f.filenode())
-
- if fid in filenodes:
- mark = filenodes[fid]
- else:
- mark = marks.next_mark()
- filenodes[fid] = mark
- d = f.data()
-
- print "blob"
- print "mark :%u" % mark
- print "data %d" % len(d)
- print d
-
- path = fix_file_path(f.path())
- final.append((gitmode(f.flags()), mark, path))
-
- return final
-
-def get_filechanges(repo, ctx, parent):
- modified = set()
- added = set()
- removed = set()
-
- # load earliest manifest first for caching reasons
- prev = parent.manifest().copy()
- cur = ctx.manifest()
-
- for fn in cur:
- if fn in prev:
- if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
- modified.add(fn)
- del prev[fn]
- else:
- added.add(fn)
- removed |= set(prev.keys())
-
- return added | modified, removed
-
-def fixup_user_git(user):
- name = mail = None
- user = user.replace('"', '')
- m = AUTHOR_RE.match(user)
- if m:
- name = m.group(1)
- mail = m.group(2).strip()
- else:
- m = EMAIL_RE.match(user)
- if m:
- mail = m.group(1)
- else:
- m = NAME_RE.match(user)
- if m:
- name = m.group(1).strip()
- return (name, mail)
-
-def fixup_user_hg(user):
- def sanitize(name):
- # stole this from hg-git
- return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
-
- m = AUTHOR_HG_RE.match(user)
- if m:
- name = sanitize(m.group(1))
- mail = sanitize(m.group(2))
- ex = m.group(3)
- if ex:
- name += ' ext:(' + urllib.quote(ex) + ')'
- else:
- name = sanitize(user)
- if '@' in user:
- mail = name
- else:
- mail = None
-
- return (name, mail)
-
-def fixup_user(user):
- if mode == 'git':
- name, mail = fixup_user_git(user)
- else:
- name, mail = fixup_user_hg(user)
-
- if not name:
- name = bad_name
- if not mail:
- mail = bad_mail
-
- return '%s <%s>' % (name, mail)
-
-def updatebookmarks(repo, peer):
- remotemarks = peer.listkeys('bookmarks')
- localmarks = repo._bookmarks
-
- if not remotemarks:
- return
-
- for k, v in remotemarks.iteritems():
- localmarks[k] = hgbin(v)
-
- if hasattr(localmarks, 'write'):
- localmarks.write()
- else:
- bookmarks.write(repo)
-
-def get_repo(url, alias):
- global peer
-
- myui = ui.ui()
- myui.setconfig('ui', 'interactive', 'off')
- myui.fout = sys.stderr
-
- if get_config_bool('remote-hg.insecure'):
- myui.setconfig('web', 'cacerts', '')
-
- extensions.loadall(myui)
-
- if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
- repo = hg.repository(myui, url)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- else:
- shared_path = os.path.join(gitdir, 'hg')
-
- # check and upgrade old organization
- hg_path = os.path.join(shared_path, '.hg')
- if os.path.exists(shared_path) and not os.path.exists(hg_path):
- repos = os.listdir(shared_path)
- for x in repos:
- local_hg = os.path.join(shared_path, x, 'clone', '.hg')
- if not os.path.exists(local_hg):
- continue
- if not os.path.exists(hg_path):
- shutil.move(local_hg, hg_path)
- shutil.rmtree(os.path.join(shared_path, x, 'clone'))
-
- # setup shared repo (if not there)
- try:
- hg.peer(myui, {}, shared_path, create=True)
- except error.RepoError:
- pass
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- local_path = os.path.join(dirname, 'clone')
- if not os.path.exists(local_path):
- hg.share(myui, shared_path, local_path, update=False)
- else:
- # make sure the shared path is always up-to-date
- util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
-
- repo = hg.repository(myui, local_path)
- try:
- peer = hg.peer(myui, {}, url)
- except:
- die('Repository error')
- repo.pull(peer, heads=None, force=True)
-
- updatebookmarks(repo, peer)
-
- return repo
-
-def rev_to_mark(rev):
- return marks.from_rev(rev.hex())
-
-def mark_to_rev(mark):
- return marks.to_rev(mark)
-
-def export_ref(repo, name, kind, head):
- ename = '%s/%s' % (kind, name)
- try:
- tip = marks.get_tip(ename)
- tip = repo[tip].rev()
- except:
- tip = 0
-
- revs = xrange(tip, head.rev() + 1)
- total = len(revs)
-
- for rev in revs:
-
- c = repo[rev]
- node = c.node()
-
- if marks.is_marked(c.hex()):
- continue
-
- (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
- rev_branch = extra['branch']
-
- author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
- if 'committer' in extra:
- user, time, tz = extra['committer'].rsplit(' ', 2)
- committer = "%s %s %s" % (user, time, gittz(int(tz)))
- else:
- committer = author
-
- parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
-
- if len(parents) == 0:
- modified = c.manifest().keys()
- removed = []
- else:
- modified, removed = get_filechanges(repo, c, parents[0])
-
- desc += '\n'
-
- if mode == 'hg':
- extra_msg = ''
-
- if rev_branch != 'default':
- extra_msg += 'branch : %s\n' % rev_branch
-
- renames = []
- for f in c.files():
- if f not in c.manifest():
- continue
- rename = c.filectx(f).renamed()
- if rename:
- renames.append((rename[0], f))
-
- for e in renames:
- extra_msg += "rename : %s => %s\n" % e
-
- for key, value in extra.iteritems():
- if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
- continue
- else:
- extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
-
- if extra_msg:
- desc += '\n--HG--\n' + extra_msg
-
- if len(parents) == 0 and rev:
- print 'reset %s/%s' % (prefix, ename)
-
- modified_final = export_files(c.filectx(f) for f in modified)
-
- print "commit %s/%s" % (prefix, ename)
- print "mark :%d" % (marks.get_mark(c.hex()))
- print "author %s" % (author)
- print "committer %s" % (committer)
- print "data %d" % (len(desc))
- print desc
-
- if len(parents) > 0:
- print "from :%s" % (rev_to_mark(parents[0]))
- if len(parents) > 1:
- print "merge :%s" % (rev_to_mark(parents[1]))
-
- for f in removed:
- print "D %s" % (fix_file_path(f))
- for f in modified_final:
- print "M %s :%u %s" % f
- print
-
- progress = (rev - tip)
- if (progress % 100 == 0):
- print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
-
- # make sure the ref is updated
- print "reset %s/%s" % (prefix, ename)
- print "from :%u" % rev_to_mark(head)
- print
-
- pending_revs = set(revs) - notes
- if pending_revs:
- note_mark = marks.next_mark()
- ref = "refs/notes/hg"
-
- print "commit %s" % ref
- print "mark :%d" % (note_mark)
- print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone))
- desc = "Notes for %s\n" % (name)
- print "data %d" % (len(desc))
- print desc
- if marks.last_note:
- print "from :%u" % marks.last_note
-
- for rev in pending_revs:
- notes.add(rev)
- c = repo[rev]
- print "N inline :%u" % rev_to_mark(c)
- msg = c.hex()
- print "data %d" % (len(msg))
- print msg
- print
-
- marks.last_note = note_mark
-
- marks.set_tip(ename, head.hex())
-
-def export_tag(repo, tag):
- export_ref(repo, tag, 'tags', repo[hgref(tag)])
-
-def export_bookmark(repo, bmark):
- head = bmarks[hgref(bmark)]
- export_ref(repo, bmark, 'bookmarks', head)
-
-def export_branch(repo, branch):
- tip = get_branch_tip(repo, branch)
- head = repo[tip]
- export_ref(repo, branch, 'branches', head)
-
-def export_head(repo):
- export_ref(repo, g_head[0], 'bookmarks', g_head[1])
-
-def do_capabilities(parser):
- print "import"
- print "export"
- print "refspec refs/heads/branches/*:%s/branches/*" % prefix
- print "refspec refs/heads/*:%s/bookmarks/*" % prefix
- print "refspec refs/tags/*:%s/tags/*" % prefix
-
- path = os.path.join(dirname, 'marks-git')
-
- if os.path.exists(path):
- print "*import-marks %s" % path
- print "*export-marks %s" % path
- print "option"
-
- print
-
-def branch_tip(branch):
- return branches[branch][-1]
-
-def get_branch_tip(repo, branch):
- heads = branches.get(hgref(branch), None)
- if not heads:
- return None
-
- # verify there's only one head
- if (len(heads) > 1):
- warn("Branch '%s' has more than one head, consider merging" % branch)
- return branch_tip(hgref(branch))
-
- return heads[0]
-
-def list_head(repo, cur):
- global g_head, fake_bmark
-
- if 'default' not in branches:
- # empty repo
- return
-
- node = repo[branch_tip('default')]
- head = 'master' if not 'master' in bmarks else 'default'
- fake_bmark = head
- bmarks[head] = node
-
- head = gitref(head)
- print "@refs/heads/%s HEAD" % head
- g_head = (head, node)
-
-def do_list(parser):
- repo = parser.repo
- for bmark, node in bookmarks.listbookmarks(repo).iteritems():
- bmarks[bmark] = repo[node]
-
- cur = repo.dirstate.branch()
- orig = peer if peer else repo
-
- for branch, heads in orig.branchmap().iteritems():
- # only open heads
- heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
- if heads:
- branches[branch] = heads
-
- list_head(repo, cur)
-
- if track_branches:
- for branch in branches:
- print "? refs/heads/branches/%s" % gitref(branch)
-
- for bmark in bmarks:
- if bmarks[bmark].hex() == '0000000000000000000000000000000000000000':
- warn("Ignoring invalid bookmark '%s'", bmark)
- else:
- print "? refs/heads/%s" % gitref(bmark)
-
- for tag, node in repo.tagslist():
- if tag == 'tip':
- continue
- print "? refs/tags/%s" % gitref(tag)
-
- print
-
-def do_import(parser):
- repo = parser.repo
-
- path = os.path.join(dirname, 'marks-git')
-
- print "feature done"
- if os.path.exists(path):
- print "feature import-marks=%s" % path
- print "feature export-marks=%s" % path
- print "feature force"
- sys.stdout.flush()
-
- tmp = encoding.encoding
- encoding.encoding = 'utf-8'
-
- # lets get all the import lines
- while parser.check('import'):
- ref = parser[1]
-
- if (ref == 'HEAD'):
- export_head(repo)
- elif ref.startswith('refs/heads/branches/'):
- branch = ref[len('refs/heads/branches/'):]
- export_branch(repo, branch)
- elif ref.startswith('refs/heads/'):
- bmark = ref[len('refs/heads/'):]
- export_bookmark(repo, bmark)
- elif ref.startswith('refs/tags/'):
- tag = ref[len('refs/tags/'):]
- export_tag(repo, tag)
-
- parser.next()
-
- encoding.encoding = tmp
-
- print 'done'
-
-def parse_blob(parser):
- parser.next()
- mark = parser.get_mark()
- parser.next()
- data = parser.get_data()
- blob_marks[mark] = data
- parser.next()
-
-def get_merge_files(repo, p1, p2, files):
- for e in repo[p1].files():
- if e not in files:
- if e not in repo[p1].manifest():
- continue
- f = { 'ctx' : repo[p1][e] }
- files[e] = f
-
-def c_style_unescape(string):
- if string[0] == string[-1] == '"':
- return string.decode('string-escape')[1:-1]
- return string
-
-def parse_commit(parser):
- from_mark = merge_mark = None
-
- ref = parser[1]
- parser.next()
-
- commit_mark = parser.get_mark()
- parser.next()
- author = parser.get_author()
- parser.next()
- committer = parser.get_author()
- parser.next()
- data = parser.get_data()
- parser.next()
- if parser.check('from'):
- from_mark = parser.get_mark()
- parser.next()
- if parser.check('merge'):
- merge_mark = parser.get_mark()
- parser.next()
- if parser.check('merge'):
- die('octopus merges are not supported yet')
-
- # fast-export adds an extra newline
- if data[-1] == '\n':
- data = data[:-1]
-
- files = {}
-
- for line in parser:
- if parser.check('M'):
- t, m, mark_ref, path = line.split(' ', 3)
- mark = int(mark_ref[1:])
- f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
- elif parser.check('D'):
- t, path = line.split(' ', 1)
- f = { 'deleted' : True }
- else:
- die('Unknown file command: %s' % line)
- path = c_style_unescape(path)
- files[path] = f
-
- # only export the commits if we are on an internal proxy repo
- if dry_run and not peer:
- parsed_refs[ref] = None
- return
-
- def getfilectx(repo, memctx, f):
- of = files[f]
- if 'deleted' in of:
- raise IOError
- if 'ctx' in of:
- return of['ctx']
- is_exec = of['mode'] == 'x'
- is_link = of['mode'] == 'l'
- rename = of.get('rename', None)
- return context.memfilectx(f, of['data'],
- is_link, is_exec, rename)
-
- repo = parser.repo
-
- user, date, tz = author
- extra = {}
-
- if committer != author:
- extra['committer'] = "%s %u %u" % committer
-
- if from_mark:
- p1 = mark_to_rev(from_mark)
- else:
- p1 = '0' * 40
-
- if merge_mark:
- p2 = mark_to_rev(merge_mark)
- else:
- p2 = '0' * 40
-
- #
- # If files changed from any of the parents, hg wants to know, but in git if
- # nothing changed from the first parent, nothing changed.
- #
- if merge_mark:
- get_merge_files(repo, p1, p2, files)
-
- # Check if the ref is supposed to be a named branch
- if ref.startswith('refs/heads/branches/'):
- branch = ref[len('refs/heads/branches/'):]
- extra['branch'] = hgref(branch)
-
- if mode == 'hg':
- i = data.find('\n--HG--\n')
- if i >= 0:
- tmp = data[i + len('\n--HG--\n'):].strip()
- for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
- if k == 'rename':
- old, new = v.split(' => ', 1)
- files[new]['rename'] = old
- elif k == 'branch':
- extra[k] = v
- elif k == 'extra':
- ek, ev = v.split(' : ', 1)
- extra[ek] = urllib.unquote(ev)
- data = data[:i]
-
- ctx = context.memctx(repo, (p1, p2), data,
- files.keys(), getfilectx,
- user, (date, tz), extra)
-
- tmp = encoding.encoding
- encoding.encoding = 'utf-8'
-
- node = hghex(repo.commitctx(ctx))
-
- encoding.encoding = tmp
-
- parsed_refs[ref] = node
- marks.new_mark(node, commit_mark)
-
-def parse_reset(parser):
- ref = parser[1]
- parser.next()
- # ugh
- if parser.check('commit'):
- parse_commit(parser)
- return
- if not parser.check('from'):
- return
- from_mark = parser.get_mark()
- parser.next()
-
- try:
- rev = mark_to_rev(from_mark)
- except KeyError:
- rev = None
- parsed_refs[ref] = rev
-
-def parse_tag(parser):
- name = parser[1]
- parser.next()
- from_mark = parser.get_mark()
- parser.next()
- tagger = parser.get_author()
- parser.next()
- data = parser.get_data()
- parser.next()
-
- parsed_tags[name] = (tagger, data)
-
-def write_tag(repo, tag, node, msg, author):
- branch = repo[node].branch()
- tip = branch_tip(branch)
- tip = repo[tip]
-
- def getfilectx(repo, memctx, f):
- try:
- fctx = tip.filectx(f)
- data = fctx.data()
- except error.ManifestLookupError:
- data = ""
- content = data + "%s %s\n" % (node, tag)
- return context.memfilectx(f, content, False, False, None)
-
- p1 = tip.hex()
- p2 = '0' * 40
- if author:
- user, date, tz = author
- date_tz = (date, tz)
- else:
- cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- output, _ = process.communicate()
- m = re.match('^.* <.*>', output)
- if m:
- user = m.group(0)
- else:
- user = repo.ui.username()
- date_tz = None
-
- ctx = context.memctx(repo, (p1, p2), msg,
- ['.hgtags'], getfilectx,
- user, date_tz, {'branch' : branch})
-
- tmp = encoding.encoding
- encoding.encoding = 'utf-8'
-
- tagnode = repo.commitctx(ctx)
-
- encoding.encoding = tmp
-
- return (tagnode, branch)
-
-def checkheads_bmark(repo, ref, ctx):
- bmark = ref[len('refs/heads/'):]
- if not bmark in bmarks:
- # new bmark
- return True
-
- ctx_old = bmarks[bmark]
- ctx_new = ctx
- if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
- if force_push:
- print "ok %s forced update" % ref
- else:
- print "error %s non-fast forward" % ref
- return False
-
- return True
-
-def checkheads(repo, remote, p_revs):
-
- remotemap = remote.branchmap()
- if not remotemap:
- # empty repo
- return True
-
- new = {}
- ret = True
-
- for node, ref in p_revs.iteritems():
- ctx = repo[node]
- branch = ctx.branch()
- if not branch in remotemap:
- # new branch
- continue
- if not ref.startswith('refs/heads/branches'):
- if ref.startswith('refs/heads/'):
- if not checkheads_bmark(repo, ref, ctx):
- ret = False
-
- # only check branches
- continue
- new.setdefault(branch, []).append(ctx.rev())
-
- for branch, heads in new.iteritems():
- old = [repo.changelog.rev(x) for x in remotemap[branch]]
- for rev in heads:
- if check_version(2, 3):
- ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
- else:
- ancestors = repo.changelog.ancestors(rev)
- found = False
-
- for x in old:
- if x in ancestors:
- found = True
- break
-
- if found:
- continue
-
- node = repo.changelog.node(rev)
- ref = p_revs[node]
- if force_push:
- print "ok %s forced update" % ref
- else:
- print "error %s non-fast forward" % ref
- ret = False
-
- return ret
-
-def push_unsafe(repo, remote, parsed_refs, p_revs):
-
- force = force_push
-
- fci = discovery.findcommonincoming
- commoninc = fci(repo, remote, force=force)
- common, _, remoteheads = commoninc
-
- if not checkheads(repo, remote, p_revs):
- return None
-
- cg = repo.getbundle('push', heads=list(p_revs), common=common)
-
- unbundle = remote.capable('unbundle')
- if unbundle:
- if force:
- remoteheads = ['force']
- return remote.unbundle(cg, remoteheads, 'push')
- else:
- return remote.addchangegroup(cg, 'push', repo.url())
-
-def push(repo, remote, parsed_refs, p_revs):
- if hasattr(remote, 'canpush') and not remote.canpush():
- print "error cannot push"
-
- if not p_revs:
- # nothing to push
- return
-
- lock = None
- unbundle = remote.capable('unbundle')
- if not unbundle:
- lock = remote.lock()
- try:
- ret = push_unsafe(repo, remote, parsed_refs, p_revs)
- finally:
- if lock is not None:
- lock.release()
-
- return ret
-
-def check_tip(ref, kind, name, heads):
- try:
- ename = '%s/%s' % (kind, name)
- tip = marks.get_tip(ename)
- except KeyError:
- return True
- else:
- return tip in heads
-
-def do_export(parser):
- p_bmarks = []
- p_revs = {}
-
- parser.next()
-
- for line in parser.each_block('done'):
- if parser.check('blob'):
- parse_blob(parser)
- elif parser.check('commit'):
- parse_commit(parser)
- elif parser.check('reset'):
- parse_reset(parser)
- elif parser.check('tag'):
- parse_tag(parser)
- elif parser.check('feature'):
- pass
- else:
- die('unhandled export command: %s' % line)
-
- need_fetch = False
-
- for ref, node in parsed_refs.iteritems():
- bnode = hgbin(node) if node else None
- if ref.startswith('refs/heads/branches'):
- branch = ref[len('refs/heads/branches/'):]
- if branch in branches and bnode in branches[branch]:
- # up to date
- continue
-
- if peer:
- remotemap = peer.branchmap()
- if remotemap and branch in remotemap:
- heads = [hghex(e) for e in remotemap[branch]]
- if not check_tip(ref, 'branches', branch, heads):
- print "error %s fetch first" % ref
- need_fetch = True
- continue
-
- p_revs[bnode] = ref
- print "ok %s" % ref
- elif ref.startswith('refs/heads/'):
- bmark = ref[len('refs/heads/'):]
- new = node
- old = bmarks[bmark].hex() if bmark in bmarks else ''
-
- if old == new:
- continue
-
- print "ok %s" % ref
- if bmark != fake_bmark and \
- not (bmark == 'master' and bmark not in parser.repo._bookmarks):
- p_bmarks.append((ref, bmark, old, new))
-
- if peer:
- remote_old = peer.listkeys('bookmarks').get(bmark)
- if remote_old:
- if not check_tip(ref, 'bookmarks', bmark, remote_old):
- print "error %s fetch first" % ref
- need_fetch = True
- continue
-
- p_revs[bnode] = ref
- elif ref.startswith('refs/tags/'):
- if dry_run:
- print "ok %s" % ref
- continue
- tag = ref[len('refs/tags/'):]
- tag = hgref(tag)
- author, msg = parsed_tags.get(tag, (None, None))
- if mode == 'git':
- if not msg:
- msg = 'Added tag %s for changeset %s' % (tag, node[:12])
- tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
- p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
- else:
- fp = parser.repo.opener('localtags', 'a')
- fp.write('%s %s\n' % (node, tag))
- fp.close()
- p_revs[bnode] = ref
- print "ok %s" % ref
- else:
- # transport-helper/fast-export bugs
- continue
-
- if need_fetch:
- print
- return
-
- if dry_run:
- if peer and not force_push:
- checkheads(parser.repo, peer, p_revs)
- print
- return
-
- if peer:
- if not push(parser.repo, peer, parsed_refs, p_revs):
- # do not update bookmarks
- print
- return
-
- # update remote bookmarks
- remote_bmarks = peer.listkeys('bookmarks')
- for ref, bmark, old, new in p_bmarks:
- if force_push:
- old = remote_bmarks.get(bmark, '')
- if not peer.pushkey('bookmarks', bmark, old, new):
- print "error %s" % ref
- else:
- # update local bookmarks
- for ref, bmark, old, new in p_bmarks:
- if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
- print "error %s" % ref
-
- print
-
-def do_option(parser):
- global dry_run, force_push
- _, key, value = parser.line.split(' ')
- if key == 'dry-run':
- dry_run = (value == 'true')
- print 'ok'
- elif key == 'force':
- force_push = (value == 'true')
- print 'ok'
- else:
- print 'unsupported'
-
-def fix_path(alias, repo, orig_url):
- url = urlparse.urlparse(orig_url, 'file')
- if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
- return
- abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
- cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
- subprocess.call(cmd)
-
-def main(args):
- global prefix, gitdir, dirname, branches, bmarks
- global marks, blob_marks, parsed_refs
- global peer, mode, bad_mail, bad_name
- global track_branches, force_push, is_tmp
- global parsed_tags
- global filenodes
- global fake_bmark, hg_version
- global dry_run
- global notes, alias
-
- marks = None
- is_tmp = False
- gitdir = os.environ.get('GIT_DIR', None)
-
- if len(args) < 3:
- die('Not enough arguments.')
-
- if not gitdir:
- die('GIT_DIR not set')
-
- alias = args[1]
- url = args[2]
- peer = None
-
- hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
- track_branches = get_config_bool('remote-hg.track-branches', True)
- force_push = False
-
- if hg_git_compat:
- mode = 'hg'
- bad_mail = 'none@none'
- bad_name = ''
- else:
- mode = 'git'
- bad_mail = 'unknown'
- bad_name = 'Unknown'
-
- if alias[4:] == url:
- is_tmp = True
- alias = hashlib.sha1(alias).hexdigest()
-
- dirname = os.path.join(gitdir, 'hg', alias)
- branches = {}
- bmarks = {}
- blob_marks = {}
- parsed_refs = {}
- parsed_tags = {}
- filenodes = {}
- fake_bmark = None
- try:
- hg_version = tuple(int(e) for e in util.version().split('.'))
- except:
- hg_version = None
- dry_run = False
- notes = set()
-
- repo = get_repo(url, alias)
- prefix = 'refs/hg/%s' % alias
-
- if not is_tmp:
- fix_path(alias, peer or repo, url)
-
- marks_path = os.path.join(dirname, 'marks-hg')
- marks = Marks(marks_path, repo)
-
- if sys.platform == 'win32':
- import msvcrt
- msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
-
- parser = Parser(repo)
- for line in parser:
- if parser.check('capabilities'):
- do_capabilities(parser)
- elif parser.check('list'):
- do_list(parser)
- elif parser.check('import'):
- do_import(parser)
- elif parser.check('export'):
- do_export(parser)
- elif parser.check('option'):
- do_option(parser)
- else:
- die('unhandled command: %s' % line)
- sys.stdout.flush()
-def bye():
- if not marks:
- return
- if not is_tmp:
- marks.store()
- else:
- shutil.rmtree(dirname)
+sys.stderr.write('WARNING: git-remote-hg is now maintained independently.\n')
+sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-hg\n')
-atexit.register(bye)
-sys.exit(main(sys.argv))
+sys.stderr.write('''WARNING:
+WARNING: You can pick a directory on your $PATH and download it, e.g.:
+WARNING: $ wget -O $HOME/bin/git-remote-hg \\
+WARNING: https://raw.github.com/felipec/git-remote-hg/master/git-remote-hg
+WARNING: $ chmod +x $HOME/bin/git-remote-hg
+''')
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-
-test_description='Test remote-bzr'
-
-test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
-. "$TEST_DIRECTORY"/test-lib.sh
-
-if ! test_have_prereq PYTHON
-then
- skip_all='skipping remote-bzr tests; python not available'
- test_done
-fi
-
-if ! python -c 'import bzrlib'
-then
- skip_all='skipping remote-bzr tests; bzr not available'
- test_done
-fi
-
-check () {
- echo $3 >expected &&
- git --git-dir=$1/.git log --format='%s' -1 $2 >actual
- test_cmp expected actual
-}
-
-bzr whoami "A U Thor <author@example.com>"
-
-test_expect_success 'cloning' '
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
- echo one >content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::bzrrepo" gitrepo &&
- check gitrepo HEAD one
-'
-
-test_expect_success 'pulling' '
- (
- cd bzrrepo &&
- echo two >content &&
- bzr commit -m two
- ) &&
-
- (cd gitrepo && git pull) &&
-
- check gitrepo HEAD two
-'
-
-test_expect_success 'pushing' '
- (
- cd gitrepo &&
- echo three >content &&
- git commit -a -m three &&
- git push
- ) &&
-
- echo three >expected &&
- cat bzrrepo/content >actual &&
- test_cmp expected actual
-'
-
-test_expect_success 'forced pushing' '
- (
- cd gitrepo &&
- echo three-new >content &&
- git commit -a --amend -m three-new &&
- git push -f
- ) &&
-
- (
- cd bzrrepo &&
- # the forced update overwrites the bzr branch but not the bzr
- # working directory (it tries to merge instead)
- bzr revert
- ) &&
-
- echo three-new >expected &&
- cat bzrrepo/content >actual &&
- test_cmp expected actual
-'
-
-test_expect_success 'roundtrip' '
- (
- cd gitrepo &&
- git pull &&
- git log --format="%s" -1 origin/master >actual
- ) &&
- echo three-new >expected &&
- test_cmp expected actual &&
-
- (cd gitrepo && git push && git pull) &&
-
- (
- cd bzrrepo &&
- echo four >content &&
- bzr commit -m four
- ) &&
-
- (cd gitrepo && git pull && git push) &&
-
- check gitrepo HEAD four &&
-
- (
- cd gitrepo &&
- echo five >content &&
- git commit -a -m five &&
- git push && git pull
- ) &&
-
- (cd bzrrepo && bzr revert) &&
-
- echo five >expected &&
- cat bzrrepo/content >actual &&
- test_cmp expected actual
-'
-
-cat >expected <<\EOF
-100644 blob 54f9d6da5c91d556e6b54340b1327573073030af content
-100755 blob 68769579c3eaadbe555379b9c3538e6628bae1eb executable
-120000 blob 6b584e8ece562ebffc15d38808cd6b98fc3d97ea link
-EOF
-
-test_expect_success 'special modes' '
- (
- cd bzrrepo &&
- echo exec >executable
- chmod +x executable &&
- bzr add executable
- bzr commit -m exec &&
- ln -s content link
- bzr add link
- bzr commit -m link &&
- mkdir dir &&
- bzr add dir &&
- bzr commit -m dir
- ) &&
-
- (
- cd gitrepo &&
- git pull
- git ls-tree HEAD >../actual
- ) &&
-
- test_cmp expected actual &&
-
- (
- cd gitrepo &&
- git cat-file -p HEAD:link >../actual
- ) &&
-
- printf content >expected &&
- test_cmp expected actual
-'
-
-cat >expected <<\EOF
-100644 blob 54f9d6da5c91d556e6b54340b1327573073030af content
-100755 blob 68769579c3eaadbe555379b9c3538e6628bae1eb executable
-120000 blob 6b584e8ece562ebffc15d38808cd6b98fc3d97ea link
-040000 tree 35c0caa46693cef62247ac89a680f0c5ce32b37b movedir-new
-EOF
-
-test_expect_success 'moving directory' '
- (
- cd bzrrepo &&
- mkdir movedir &&
- echo one >movedir/one &&
- echo two >movedir/two &&
- bzr add movedir &&
- bzr commit -m movedir &&
- bzr mv movedir movedir-new &&
- bzr commit -m movedir-new
- ) &&
-
- (
- cd gitrepo &&
- git pull &&
- git ls-tree HEAD >../actual
- ) &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'different authors' '
- (
- cd bzrrepo &&
- echo john >>content &&
- bzr commit -m john \
- --author "Jane Rey <jrey@example.com>" \
- --author "John Doe <jdoe@example.com>"
- ) &&
-
- (
- cd gitrepo &&
- git pull &&
- git show --format="%an <%ae>, %cn <%ce>" --quiet >../actual
- ) &&
-
- echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" >expected &&
- test_cmp expected actual
-'
-
-# cleanup previous stuff
-rm -rf bzrrepo gitrepo
-
-test_expect_success 'fetch utf-8 filenames' '
- test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
-
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
-
- echo test >>"ærø" &&
- bzr add "ærø" &&
- echo test >>"ø~?" &&
- bzr add "ø~?" &&
- bzr commit -m add-utf-8 &&
- echo test >>"ærø" &&
- bzr commit -m test-utf-8 &&
- bzr rm "ø~?" &&
- bzr mv "ærø" "ø~?" &&
- bzr commit -m bzr-mv-utf-8
- ) &&
-
- (
- git clone "bzr::bzrrepo" gitrepo &&
- cd gitrepo &&
- git -c core.quotepath=false ls-files >../actual
- ) &&
- echo "ø~?" >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'push utf-8 filenames' '
- test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
-
- mkdir -p tmp && cd tmp &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
-
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
-
- echo one >>content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- (
- git clone "bzr::bzrrepo" gitrepo &&
- cd gitrepo &&
-
- echo test >>"ærø" &&
- git add "ærø" &&
- git commit -m utf-8 &&
-
- git push
- ) &&
-
- (cd bzrrepo && bzr ls >../actual) &&
- printf "content\nærø\n" >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'pushing a merge' '
- test_when_finished "rm -rf bzrrepo gitrepo" &&
-
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
- echo one >content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::bzrrepo" gitrepo &&
-
- (
- cd bzrrepo &&
- echo two >content &&
- bzr commit -m two
- ) &&
-
- (
- cd gitrepo &&
- echo three >content &&
- git commit -a -m three &&
- git fetch &&
- git merge origin/master || true &&
- echo three >content &&
- git commit -a --no-edit &&
- git push
- ) &&
-
- echo three >expected &&
- cat bzrrepo/content >actual &&
- test_cmp expected actual
-'
-
-cat >expected <<\EOF
-origin/HEAD
-origin/branch
-origin/trunk
-EOF
-
-test_expect_success 'proper bzr repo' '
- test_when_finished "rm -rf bzrrepo gitrepo" &&
-
- bzr init-repo bzrrepo &&
-
- (
- bzr init bzrrepo/trunk &&
- cd bzrrepo/trunk &&
- echo one >>content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- (
- bzr branch bzrrepo/trunk bzrrepo/branch &&
- cd bzrrepo/branch &&
- echo two >>content &&
- bzr commit -m one
- ) &&
-
- (
- git clone "bzr::bzrrepo" gitrepo &&
- cd gitrepo &&
- git for-each-ref --format "%(refname:short)" refs/remotes/origin >../actual
- ) &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'strip' '
- test_when_finished "rm -rf bzrrepo gitrepo" &&
-
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
-
- echo one >>content &&
- bzr add content &&
- bzr commit -m one &&
-
- echo two >>content &&
- bzr commit -m two
- ) &&
-
- git clone "bzr::bzrrepo" gitrepo &&
-
- (
- cd bzrrepo &&
- bzr uncommit --force &&
-
- echo three >>content &&
- bzr commit -m three &&
-
- echo four >>content &&
- bzr commit -m four &&
- bzr log --line | sed -e "s/^[0-9][0-9]*: //" >../expected
- ) &&
-
- (
- cd gitrepo &&
- git fetch &&
- git log --format="%an %ad %s" --date=short origin/master >../actual
- ) &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'export utf-8 authors' '
- test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C && GIT_COMMITTER_NAME=\"C O Mitter\""
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
-
- GIT_COMMITTER_NAME="Grégoire"
- export GIT_COMMITTER_NAME
-
- bzr init bzrrepo &&
-
- (
- git init gitrepo &&
- cd gitrepo &&
- echo greg >>content &&
- git add content &&
- git commit -m one &&
- git remote add bzr "bzr::../bzrrepo" &&
- git push bzr master
- ) &&
-
- (
- cd bzrrepo &&
- bzr log | grep "^committer: " >../actual
- ) &&
-
- echo "committer: Grégoire <committer@example.com>" >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'push different author' '
- test_when_finished "rm -rf bzrrepo gitrepo" &&
-
- bzr init bzrrepo &&
-
- (
- git init gitrepo &&
- cd gitrepo &&
- echo john >> content &&
- git add content &&
- git commit -m john --author "John Doe <jdoe@example.com>" &&
- git remote add bzr "bzr::../bzrrepo" &&
- git push bzr master
- ) &&
-
- (
- cd bzrrepo &&
- bzr log | grep "^author: " > ../actual
- ) &&
-
- echo "author: John Doe <jdoe@example.com>" > expected &&
- test_cmp expected actual
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test bidirectionality of remote-hg'
-
-test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
-. "$TEST_DIRECTORY"/test-lib.sh
-
-if ! test_have_prereq PYTHON
-then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! python -c 'import mercurial'
-then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-# clone to a git repo
-git_clone () {
- git clone -q "hg::$1" $2
-}
-
-# clone to an hg repo
-hg_clone () {
- (
- hg init $2 &&
- cd $1 &&
- git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
- ) &&
-
- (cd $2 && hg -q update)
-}
-
-# push an hg repo
-hg_push () {
- (
- cd $2
- git checkout -q -b tmp &&
- git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q @{-1} &&
- git branch -q -D tmp 2>/dev/null || true
- )
-}
-
-hg_log () {
- hg -R $1 log --graph --debug
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = A U Thor <author@example.com>"
- echo "[defaults]"
- echo "backout = -d \"0 0\""
- echo "commit = -d \"0 0\""
- echo "debugrawcommit = -d \"0 0\""
- echo "tag = -d \"0 0\""
- echo "[extensions]"
- echo "graphlog ="
- ) >>"$HOME"/.hgrc &&
- git config --global remote-hg.hg-git-compat true
- git config --global remote-hg.track-branches true
-
- HGEDITOR=/usr/bin/true
- GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
- GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
- export HGEDITOR GIT_AUTHOR_DATE GIT_COMMITTER_DATE
-}
-
-setup
-
-test_expect_success 'encoding' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add älphà" &&
-
- GIT_AUTHOR_NAME="tést èncödîng" &&
- export GIT_AUTHOR_NAME &&
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta" &&
-
- echo gamma >gamma &&
- git add gamma &&
- git commit -m "add gämmâ" &&
-
- : TODO git config i18n.commitencoding latin-1 &&
- echo delta >delta &&
- git add delta &&
- git commit -m "add déltà"
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- HGENCODING=utf-8 hg_log hgrepo >expected &&
- HGENCODING=utf-8 hg_log hgrepo2 >actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'file removal' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta"
- mkdir foo &&
- echo blah >foo/bar &&
- git add foo &&
- git commit -m "add foo" &&
- git rm alpha &&
- git commit -m "remove alpha" &&
- git rm foo/bar &&
- git commit -m "remove foo/bar"
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- hg_log hgrepo >expected &&
- hg_log hgrepo2 >actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'git tags' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- git config receive.denyCurrentBranch ignore &&
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git tag alpha &&
-
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta" &&
- git tag -a -m "added tag beta" beta
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- hg_log hgrepo >expected &&
- hg_log hgrepo2 >actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'hg branch' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -q -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone gitrepo hgrepo &&
-
- cd hgrepo &&
- hg -q co default &&
- hg mv alpha beta &&
- hg -q commit -m "rename alpha to beta" &&
- hg branch gamma | grep -v "permanent and global" &&
- hg -q commit -m "started branch gamma"
- ) &&
-
- hg_push hgrepo gitrepo &&
- hg_clone gitrepo hgrepo2 &&
-
- : Back to the common revision &&
- (cd hgrepo && hg checkout default) &&
-
- hg_log hgrepo >expected &&
- hg_log hgrepo2 >actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'hg tags' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone gitrepo hgrepo &&
-
- cd hgrepo &&
- hg co default &&
- hg tag alpha
- ) &&
-
- hg_push hgrepo gitrepo &&
- hg_clone gitrepo hgrepo2 &&
-
- hg_log hgrepo >expected &&
- hg_log hgrepo2 >actual &&
-
- test_cmp expected actual
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test remote-hg output compared to hg-git'
-
-test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
-. "$TEST_DIRECTORY"/test-lib.sh
-
-if ! test_have_prereq PYTHON
-then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! python -c 'import mercurial'
-then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-if ! python -c 'import hggit'
-then
- skip_all='skipping remote-hg tests; hg-git not available'
- test_done
-fi
-
-# clone to a git repo with git
-git_clone_git () {
- git clone -q "hg::$1" $2 &&
- (cd $2 && git checkout master && git branch -D default)
-}
-
-# clone to an hg repo with git
-hg_clone_git () {
- (
- hg init $2 &&
- hg -R $2 bookmark -i master &&
- cd $1 &&
- git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
- ) &&
-
- (cd $2 && hg -q update)
-}
-
-# clone to a git repo with hg
-git_clone_hg () {
- (
- git init -q $2 &&
- cd $1 &&
- hg bookmark -i -f -r tip master &&
- hg -q push -r master ../$2 || true
- )
-}
-
-# clone to an hg repo with hg
-hg_clone_hg () {
- hg -q clone $1 $2
-}
-
-# push an hg repo with git
-hg_push_git () {
- (
- cd $2
- git checkout -q -b tmp &&
- git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git branch -D default &&
- git checkout -q @{-1} &&
- git branch -q -D tmp 2>/dev/null || true
- )
-}
-
-# push an hg git repo with hg
-hg_push_hg () {
- (
- cd $1 &&
- hg -q push ../$2 || true
- )
-}
-
-hg_log () {
- hg -R $1 log --graph --debug >log &&
- grep -v 'tag: *default/' log
-}
-
-git_log () {
- git --git-dir=$1/.git fast-export --branches
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = A U Thor <author@example.com>"
- echo "[defaults]"
- echo "backout = -d \"0 0\""
- echo "commit = -d \"0 0\""
- echo "debugrawcommit = -d \"0 0\""
- echo "tag = -d \"0 0\""
- echo "[extensions]"
- echo "hgext.bookmarks ="
- echo "hggit ="
- echo "graphlog ="
- ) >>"$HOME"/.hgrc &&
- git config --global receive.denycurrentbranch warn
- git config --global remote-hg.hg-git-compat true
- git config --global remote-hg.track-branches false
-
- HGEDITOR=true
- HGMERGE=true
-
- GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
- GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
- export HGEDITOR HGMERGE GIT_AUTHOR_DATE GIT_COMMITTER_DATE
-}
-
-setup
-
-test_expect_success 'executable bit' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha >alpha &&
- chmod 0644 alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- chmod 0755 alpha &&
- git add alpha &&
- git commit -m "set executable bit" &&
- chmod 0644 alpha &&
- git add alpha &&
- git commit -m "clear executable bit"
- ) &&
-
- for x in hg git
- do
- (
- hg_clone_$x gitrepo hgrepo-$x &&
- cd hgrepo-$x &&
- hg_log . &&
- hg manifest -r 1 -v &&
- hg manifest -v
- ) >"output-$x" &&
-
- git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x >"log-$x"
- done &&
-
- test_cmp output-hg output-git &&
- test_cmp log-hg log-git
-'
-
-test_expect_success 'symlink' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- ln -s alpha beta &&
- git add beta &&
- git commit -m "add beta"
- ) &&
-
- for x in hg git
- do
- (
- hg_clone_$x gitrepo hgrepo-$x &&
- cd hgrepo-$x &&
- hg_log . &&
- hg manifest -v
- ) >"output-$x" &&
-
- git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x >"log-$x"
- done &&
-
- test_cmp output-hg output-git &&
- test_cmp log-hg log-git
-'
-
-test_expect_success 'merge conflict 1' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A >afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B >afile &&
- hg ci -m "A->B" &&
-
- hg up -r0 &&
- echo C >afile &&
- hg ci -m "A->C" &&
-
- hg merge -r1 &&
- echo C >afile &&
- hg resolve -m afile &&
- hg ci -m "merge to C"
- ) &&
-
- for x in hg git
- do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x >"hg-log-$x" &&
- git_log gitrepo-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'merge conflict 2' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A >afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B >afile &&
- hg ci -m "A->B" &&
-
- hg up -r0 &&
- echo C >afile &&
- hg ci -m "A->C" &&
-
- hg merge -r1 || true &&
- echo B >afile &&
- hg resolve -m afile &&
- hg ci -m "merge to B"
- ) &&
-
- for x in hg git
- do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x >"hg-log-$x" &&
- git_log gitrepo-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'converged merge' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A >afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B >afile &&
- hg ci -m "A->B" &&
-
- echo C >afile &&
- hg ci -m "B->C" &&
-
- hg up -r0 &&
- echo C >afile &&
- hg ci -m "A->C" &&
-
- hg merge -r2 || true &&
- hg ci -m "merge"
- ) &&
-
- for x in hg git
- do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x >"hg-log-$x" &&
- git_log gitrepo-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'encoding' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add älphà" &&
-
- GIT_AUTHOR_NAME="tést èncödîng" &&
- export GIT_AUTHOR_NAME &&
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta" &&
-
- echo gamma >gamma &&
- git add gamma &&
- git commit -m "add gämmâ" &&
-
- : TODO git config i18n.commitencoding latin-1 &&
- echo delta >delta &&
- git add delta &&
- git commit -m "add déltà"
- ) &&
-
- for x in hg git
- do
- hg_clone_$x gitrepo hgrepo-$x &&
- git_clone_$x hgrepo-$x gitrepo2-$x &&
-
- HGENCODING=utf-8 hg_log hgrepo-$x >"hg-log-$x" &&
- git_log gitrepo2-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'file removal' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta"
- mkdir foo &&
- echo blah >foo/bar &&
- git add foo &&
- git commit -m "add foo" &&
- git rm alpha &&
- git commit -m "remove alpha" &&
- git rm foo/bar &&
- git commit -m "remove foo/bar"
- ) &&
-
- for x in hg git
- do
- (
- hg_clone_$x gitrepo hgrepo-$x &&
- cd hgrepo-$x &&
- hg_log . &&
- hg manifest -r 3 &&
- hg manifest
- ) >"output-$x" &&
-
- git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x >"log-$x"
- done &&
-
- test_cmp output-hg output-git &&
- test_cmp log-hg log-git
-'
-
-test_expect_success 'git tags' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- git config receive.denyCurrentBranch ignore &&
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git tag alpha &&
-
- echo beta >beta &&
- git add beta &&
- git commit -m "add beta" &&
- git tag -a -m "added tag beta" beta
- ) &&
-
- for x in hg git
- do
- hg_clone_$x gitrepo hgrepo-$x &&
- hg_log hgrepo-$x >"log-$x"
- done &&
-
- test_cmp log-hg log-git
-'
-
-test_expect_success 'hg author' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- for x in hg git
- do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
- cd hgrepo-$x &&
-
- hg co master &&
- echo beta >beta &&
- hg add beta &&
- hg commit -u "test" -m "add beta" &&
-
- echo gamma >>beta &&
- hg commit -u "test <test@example.com> (comment)" -m "modify beta" &&
-
- echo gamma >gamma &&
- hg add gamma &&
- hg commit -u "<test@example.com>" -m "add gamma" &&
-
- echo delta >delta &&
- hg add delta &&
- hg commit -u "name<test@example.com>" -m "add delta" &&
-
- echo epsilon >epsilon &&
- hg add epsilon &&
- hg commit -u "name <test@example.com" -m "add epsilon" &&
-
- echo zeta >zeta &&
- hg add zeta &&
- hg commit -u " test " -m "add zeta" &&
-
- echo eta >eta &&
- hg add eta &&
- hg commit -u "test < test@example.com >" -m "add eta" &&
-
- echo theta >theta &&
- hg add theta &&
- hg commit -u "test >test@example.com>" -m "add theta" &&
-
- echo iota >iota &&
- hg add iota &&
- hg commit -u "test <test <at> example <dot> com>" -m "add iota"
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- hg_log hgrepo2-$x >"hg-log-$x" &&
- git_log gitrepo-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'hg branch' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- for x in hg git
- do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -q -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
-
- cd hgrepo-$x &&
- hg -q co master &&
- hg mv alpha beta &&
- hg -q commit -m "rename alpha to beta" &&
- hg branch gamma | grep -v "permanent and global" &&
- hg -q commit -m "started branch gamma"
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- hg_log hgrepo2-$x >"hg-log-$x" &&
- git_log gitrepo-$x >"git-log-$x"
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'hg tags' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- for x in hg git
- do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha >alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
-
- cd hgrepo-$x &&
- hg co master &&
- hg tag alpha
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- (
- git --git-dir=gitrepo-$x/.git tag -l &&
- hg_log hgrepo2-$x &&
- cat hgrepo2-$x/.hgtags
- ) >"output-$x"
- done &&
-
- test_cmp output-hg output-git
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test remote-hg'
-
-test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
-. "$TEST_DIRECTORY"/test-lib.sh
-
-if ! test_have_prereq PYTHON
-then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! python -c 'import mercurial'
-then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-check () {
- echo $3 >expected &&
- git --git-dir=$1/.git log --format='%s' -1 $2 >actual
- test_cmp expected actual
-}
-
-check_branch () {
- if test -n "$3"
- then
- echo $3 >expected &&
- hg -R $1 log -r $2 --template '{desc}\n' >actual &&
- test_cmp expected actual
- else
- hg -R $1 branches >out &&
- ! grep $2 out
- fi
-}
-
-check_bookmark () {
- if test -n "$3"
- then
- echo $3 >expected &&
- hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' >actual &&
- test_cmp expected actual
- else
- hg -R $1 bookmarks >out &&
- ! grep $2 out
- fi
-}
-
-check_push () {
- expected_ret=$1 ret=0 ref_ret=0
-
- shift
- git push origin "$@" 2>error
- ret=$?
- cat error
-
- while IFS=':' read branch kind
- do
- case "$kind" in
- 'new')
- grep "^ \* \[new branch\] *${branch} -> ${branch}$" error || ref_ret=1
- ;;
- 'non-fast-forward')
- grep "^ ! \[rejected\] *${branch} -> ${branch} (non-fast-forward)$" error || ref_ret=1
- ;;
- 'fetch-first')
- grep "^ ! \[rejected\] *${branch} -> ${branch} (fetch first)$" error || ref_ret=1
- ;;
- 'forced-update')
- grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *${branch} -> ${branch} (forced update)$" error || ref_ret=1
- ;;
- '')
- grep "^ [a-f0-9]*\.\.[a-f0-9]* *${branch} -> ${branch}$" error || ref_ret=1
- ;;
- esac
- test $ref_ret -ne 0 && echo "match for '$branch' failed" && break
- done
-
- if test $expected_ret -ne $ret || test $ref_ret -ne 0
- then
- return 1
- fi
-
- return 0
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = H G Wells <wells@example.com>"
- echo "[extensions]"
- echo "mq ="
- ) >>"$HOME"/.hgrc &&
-
- GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" &&
- GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" &&
- export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-setup
-
-test_expect_success 'cloning' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo HEAD zero
-'
-
-test_expect_success 'cloning with branches' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg branch next &&
- echo next >content &&
- hg commit -m next
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo origin/branches/next next
-'
-
-test_expect_success 'cloning with bookmarks' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg checkout default &&
- hg bookmark feature-a &&
- echo feature-a >content &&
- hg commit -m feature-a
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo origin/feature-a feature-a
-'
-
-test_expect_success 'update bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg bookmark devel
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout --quiet devel &&
- echo devel >content &&
- git commit -a -m devel &&
- git push --quiet
- ) &&
-
- check_bookmark hgrepo devel devel
-'
-
-test_expect_success 'new bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout --quiet -b feature-b &&
- echo feature-b >content &&
- git commit -a -m feature-b &&
- git push --quiet origin feature-b
- ) &&
-
- check_bookmark hgrepo feature-b feature-b
-'
-
-# cleanup previous stuff
-rm -rf hgrepo
-
-author_test () {
- echo $1 >>content &&
- hg commit -u "$2" -m "add $1" &&
- echo "$3" >>../expected
-}
-
-test_expect_success 'authors' '
- test_when_finished "rm -rf hgrepo gitrepo" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- touch content &&
- hg add content &&
-
- >../expected &&
- author_test alpha "" "H G Wells <wells@example.com>" &&
- author_test beta "beta" "beta <unknown>" &&
- author_test gamma "gamma <test@example.com> (comment)" "gamma <test@example.com>" &&
- author_test delta "<delta@example.com>" "Unknown <delta@example.com>" &&
- author_test epsilon "epsilon<test@example.com>" "epsilon <test@example.com>" &&
- author_test zeta "zeta <test@example.com" "zeta <test@example.com>" &&
- author_test eta " eta " "eta <unknown>" &&
- author_test theta "theta < test@example.com >" "theta <test@example.com>" &&
- author_test iota "iota >test@example.com>" "iota <test@example.com>" &&
- author_test kappa "kappa < test <at> example <dot> com>" "kappa <unknown>" &&
- author_test lambda "lambda@example.com" "Unknown <lambda@example.com>" &&
- author_test mu "mu.mu@example.com" "Unknown <mu.mu@example.com>"
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" >actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'strip' '
- test_when_finished "rm -rf hgrepo gitrepo" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- echo one >>content &&
- hg add content &&
- hg commit -m one &&
-
- echo two >>content &&
- hg commit -m two
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
-
- (
- cd hgrepo &&
- hg strip 1 &&
-
- echo three >>content &&
- hg commit -m three &&
-
- echo four >>content &&
- hg commit -m four
- ) &&
-
- (
- cd gitrepo &&
- git fetch &&
- git log --format="%s" origin/master >../actual
- ) &&
-
- hg -R hgrepo log --template "{desc}\n" >expected &&
- test_cmp actual expected
-'
-
-test_expect_success 'remote push with master bookmark' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero &&
- hg bookmark master &&
- echo one >content &&
- hg commit -m one
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- echo two >content &&
- git commit -a -m two &&
- git push
- ) &&
-
- check_branch hgrepo default two
-'
-
-cat >expected <<\EOF
-changeset: 0:6e2126489d3d
-tag: tip
-user: A U Thor <author@example.com>
-date: Mon Jan 01 00:00:00 2007 +0230
-summary: one
-
-EOF
-
-test_expect_success 'remote push from master branch' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- hg init hgrepo &&
-
- (
- git init gitrepo &&
- cd gitrepo &&
- git remote add origin "hg::../hgrepo" &&
- echo one >content &&
- git add content &&
- git commit -a -m one &&
- git push origin master
- ) &&
-
- hg -R hgrepo log >actual &&
- cat actual &&
- test_cmp expected actual &&
-
- check_branch hgrepo default one
-'
-
-GIT_REMOTE_HG_TEST_REMOTE=1
-export GIT_REMOTE_HG_TEST_REMOTE
-
-test_expect_success 'remote cloning' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo HEAD zero
-'
-
-test_expect_success 'moving remote clone' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- mv gitrepo gitrepo2 &&
- cd gitrepo2 &&
- git fetch
- )
-'
-
-test_expect_success 'remote update bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg bookmark devel
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout --quiet devel &&
- echo devel >content &&
- git commit -a -m devel &&
- git push --quiet
- ) &&
-
- check_bookmark hgrepo devel devel
-'
-
-test_expect_success 'remote new bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout --quiet -b feature-b &&
- echo feature-b >content &&
- git commit -a -m feature-b &&
- git push --quiet origin feature-b
- ) &&
-
- check_bookmark hgrepo feature-b feature-b
-'
-
-test_expect_success 'remote push diverged' '
- test_when_finished "rm -rf gitrepo*" &&
-
- git clone "hg::hgrepo" gitrepo &&
-
- (
- cd hgrepo &&
- hg checkout default &&
- echo bump >content &&
- hg commit -m bump
- ) &&
-
- (
- cd gitrepo &&
- echo diverge >content &&
- git commit -a -m diverged &&
- check_push 1 <<-\EOF
- master:non-fast-forward
- EOF
- ) &&
-
- check_branch hgrepo default bump
-'
-
-test_expect_success 'remote update bookmark diverge' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg checkout tip^ &&
- hg bookmark diverge
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
-
- (
- cd hgrepo &&
- echo "bump bookmark" >content &&
- hg commit -m "bump bookmark"
- ) &&
-
- (
- cd gitrepo &&
- git checkout --quiet diverge &&
- echo diverge >content &&
- git commit -a -m diverge &&
- check_push 1 <<-\EOF
- diverge:fetch-first
- EOF
- ) &&
-
- check_bookmark hgrepo diverge "bump bookmark"
-'
-
-test_expect_success 'remote new bookmark multiple branch head' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout --quiet -b feature-c HEAD^ &&
- echo feature-c >content &&
- git commit -a -m feature-c &&
- git push --quiet origin feature-c
- ) &&
-
- check_bookmark hgrepo feature-c feature-c
-'
-
-# cleanup previous stuff
-rm -rf hgrepo
-
-test_expect_success 'fetch special filenames' '
- test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- echo test >> "æ rø" &&
- hg add "æ rø" &&
- echo test >> "ø~?" &&
- hg add "ø~?" &&
- hg commit -m add-utf-8 &&
- echo test >> "æ rø" &&
- hg commit -m test-utf-8 &&
- hg rm "ø~?" &&
- hg mv "æ rø" "ø~?" &&
- hg commit -m hg-mv-utf-8
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git -c core.quotepath=false ls-files > ../actual
- ) &&
- echo "ø~?" > expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'push special filenames' '
- test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
-
- mkdir -p tmp && cd tmp &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- echo one >> content &&
- hg add content &&
- hg commit -m one
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
-
- echo test >> "æ rø" &&
- git add "æ rø" &&
- git commit -m utf-8 &&
-
- git push
- ) &&
-
- (cd hgrepo &&
- hg update &&
- hg manifest > ../actual
- ) &&
-
- printf "content\næ rø\n" > expected &&
- test_cmp expected actual
-'
-
-setup_big_push () {
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero &&
- hg bookmark bad_bmark1 &&
- echo one >content &&
- hg commit -m one &&
- hg bookmark bad_bmark2 &&
- hg bookmark good_bmark &&
- hg bookmark -i good_bmark &&
- hg -q branch good_branch &&
- echo "good branch" >content &&
- hg commit -m "good branch" &&
- hg -q branch bad_branch &&
- echo "bad branch" >content &&
- hg commit -m "bad branch"
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
-
- (
- cd gitrepo &&
- echo two >content &&
- git commit -q -a -m two &&
-
- git checkout -q good_bmark &&
- echo three >content &&
- git commit -q -a -m three &&
-
- git checkout -q bad_bmark1 &&
- git reset --hard HEAD^ &&
- echo four >content &&
- git commit -q -a -m four &&
-
- git checkout -q bad_bmark2 &&
- git reset --hard HEAD^ &&
- echo five >content &&
- git commit -q -a -m five &&
-
- git checkout -q -b new_bmark master &&
- echo six >content &&
- git commit -q -a -m six &&
-
- git checkout -q branches/good_branch &&
- echo seven >content &&
- git commit -q -a -m seven &&
- echo eight >content &&
- git commit -q -a -m eight &&
-
- git checkout -q branches/bad_branch &&
- git reset --hard HEAD^ &&
- echo nine >content &&
- git commit -q -a -m nine &&
-
- git checkout -q -b branches/new_branch master &&
- echo ten >content &&
- git commit -q -a -m ten
- )
-}
-
-test_expect_success 'remote big push' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- setup_big_push
-
- (
- cd gitrepo &&
-
- check_push 1 --all <<-\EOF
- master
- good_bmark
- branches/good_branch
- new_bmark:new
- branches/new_branch:new
- bad_bmark1:non-fast-forward
- bad_bmark2:non-fast-forward
- branches/bad_branch:non-fast-forward
- EOF
- ) &&
-
- check_branch hgrepo default one &&
- check_branch hgrepo good_branch "good branch" &&
- check_branch hgrepo bad_branch "bad branch" &&
- check_branch hgrepo new_branch '' &&
- check_bookmark hgrepo good_bmark one &&
- check_bookmark hgrepo bad_bmark1 one &&
- check_bookmark hgrepo bad_bmark2 one &&
- check_bookmark hgrepo new_bmark ''
-'
-
-test_expect_success 'remote big push fetch first' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero &&
- hg bookmark bad_bmark &&
- hg bookmark good_bmark &&
- hg bookmark -i good_bmark &&
- hg -q branch good_branch &&
- echo "good branch" >content &&
- hg commit -m "good branch" &&
- hg -q branch bad_branch &&
- echo "bad branch" >content &&
- hg commit -m "bad branch"
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
-
- (
- cd hgrepo &&
- hg bookmark -f bad_bmark &&
- echo update_bmark >content &&
- hg commit -m "update bmark"
- ) &&
-
- (
- cd gitrepo &&
- echo two >content &&
- git commit -q -a -m two &&
-
- git checkout -q good_bmark &&
- echo three >content &&
- git commit -q -a -m three &&
-
- git checkout -q bad_bmark &&
- echo four >content &&
- git commit -q -a -m four &&
-
- git checkout -q branches/bad_branch &&
- echo five >content &&
- git commit -q -a -m five &&
-
- check_push 1 --all <<-\EOF &&
- master
- good_bmark
- bad_bmark:fetch-first
- branches/bad_branch:festch-first
- EOF
-
- git fetch &&
-
- check_push 1 --all <<-\EOF
- master
- good_bmark
- bad_bmark:non-fast-forward
- branches/bad_branch:non-fast-forward
- EOF
- )
-'
-
-test_expect_success 'remote big push force' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- setup_big_push
-
- (
- cd gitrepo &&
-
- check_push 0 --force --all <<-\EOF
- master
- good_bmark
- branches/good_branch
- new_bmark:new
- branches/new_branch:new
- bad_bmark1:forced-update
- bad_bmark2:forced-update
- branches/bad_branch:forced-update
- EOF
- ) &&
-
- check_branch hgrepo default six &&
- check_branch hgrepo good_branch eight &&
- check_branch hgrepo bad_branch nine &&
- check_branch hgrepo new_branch ten &&
- check_bookmark hgrepo good_bmark three &&
- check_bookmark hgrepo bad_bmark1 four &&
- check_bookmark hgrepo bad_bmark2 five &&
- check_bookmark hgrepo new_bmark six
-'
-
-test_expect_success 'remote big push dry-run' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- setup_big_push
-
- (
- cd gitrepo &&
-
- check_push 1 --dry-run --all <<-\EOF &&
- master
- good_bmark
- branches/good_branch
- new_bmark:new
- branches/new_branch:new
- bad_bmark1:non-fast-forward
- bad_bmark2:non-fast-forward
- branches/bad_branch:non-fast-forward
- EOF
-
- check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-\EOF
- master
- good_bmark
- branches/good_branch
- new_bmark:new
- branches/new_branch:new
- EOF
- ) &&
-
- check_branch hgrepo default one &&
- check_branch hgrepo good_branch "good branch" &&
- check_branch hgrepo bad_branch "bad branch" &&
- check_branch hgrepo new_branch '' &&
- check_bookmark hgrepo good_bmark one &&
- check_bookmark hgrepo bad_bmark1 one &&
- check_bookmark hgrepo bad_bmark2 one &&
- check_bookmark hgrepo new_bmark ''
-'
-
-test_expect_success 'remote double failed push' '
- test_when_finished "rm -rf hgrepo gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero >content &&
- hg add content &&
- hg commit -m zero &&
- echo one >content &&
- hg commit -m one
- ) &&
-
- (
- git clone "hg::hgrepo" gitrepo &&
- cd gitrepo &&
- git reset --hard HEAD^ &&
- echo two >content &&
- git commit -a -m two &&
- test_expect_code 1 git push &&
- test_expect_code 1 git push
- )
-'
-
-test_expect_success 'clone remote with master null bookmark, then push to the bookmark' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- hg init hgrepo &&
- (
- cd hgrepo &&
- echo a >a &&
- hg add a &&
- hg commit -m a &&
- hg bookmark -r null master
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo HEAD a &&
- (
- cd gitrepo &&
- git checkout --quiet -b master &&
- echo b >b &&
- git add b &&
- git commit -m b &&
- git push origin master
- )
-'
-
-test_expect_success 'clone remote with default null bookmark, then push to the bookmark' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- hg init hgrepo &&
- (
- cd hgrepo &&
- echo a >a &&
- hg add a &&
- hg commit -m a &&
- hg bookmark -r null -f default
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo HEAD a &&
- (
- cd gitrepo &&
- git checkout --quiet -b default &&
- echo b >b &&
- git add b &&
- git commit -m b &&
- git push origin default
- )
-'
-
-test_expect_success 'clone remote with generic null bookmark, then push to the bookmark' '
- test_when_finished "rm -rf gitrepo* hgrepo*" &&
-
- hg init hgrepo &&
- (
- cd hgrepo &&
- echo a >a &&
- hg add a &&
- hg commit -m a &&
- hg bookmark -r null bmark
- ) &&
-
- git clone "hg::hgrepo" gitrepo &&
- check gitrepo HEAD a &&
- (
- cd gitrepo &&
- git checkout --quiet -b bmark &&
- git remote -v &&
- echo b >b &&
- git add b &&
- git commit -m b &&
- git push origin bmark
- )
-'
-
-test_done
prefix ?= /usr/local
mandir ?= $(prefix)/share/man
-libexecdir ?= $(prefix)/libexec/git-core
-gitdir ?= $(shell git --exec-path)
+gitexecdir ?= $(prefix)/libexec/git-core
man1dir ?= $(mandir)/man1
-gitver ?= $(word 3,$(shell git --version))
+../../GIT-VERSION-FILE: FORCE
+ $(MAKE) -C ../../ GIT-VERSION-FILE
+
+-include ../../GIT-VERSION-FILE
# this should be set to a 'standard' bsd-type install program
-INSTALL ?= install
+INSTALL ?= install
+RM ?= rm -f
+
+ASCIIDOC = asciidoc
+XMLTO = xmlto
-ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
-MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl
+ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
+MANPAGE_XSL = ../../Documentation/manpage-normal.xsl
GIT_SUBTREE_SH := git-subtree.sh
GIT_SUBTREE := git-subtree
doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML)
install: $(GIT_SUBTREE)
- $(INSTALL) -d -m 755 $(DESTDIR)$(libexecdir)
- $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(libexecdir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
+ $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir)
install-doc: install-man
$(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir)
$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
- xmlto -m $(MANPAGE_NORMAL_XSL) man $^
+ $(XMLTO) -m $(MANPAGE_XSL) man $^
$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
- asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
- -agit_version=$(gitver) $^
+ $(ASCIIDOC) -b docbook -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $^
$(GIT_SUBTREE_HTML): $(GIT_SUBTREE_TXT)
- asciidoc -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \
- -agit_version=$(gitver) $^
+ $(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $^
test:
$(MAKE) -C t/ test
clean:
- rm -f *~ *.xml *.html *.1
- rm -rf subproj mainline
+ $(RM) $(GIT_SUBTREE)
+ $(RM) *.xml *.html *.1
+
+.PHONY: FORCE
commit=$(add_squashed_msg "$rev" "$dir" |
git commit-tree $tree $headp -p "$rev") || exit $?
else
+ revp=$(peel_committish "$rev") &&
commit=$(add_msg "$dir" "$headrev" "$rev" |
- git commit-tree $tree $headp -p "$rev") || exit $?
+ git commit-tree $tree $headp -p "$revp") || exit $?
fi
git reset "$commit" || exit $?
and split subcommands of git subtree.
'
-export TEST_DIRECTORY=$(pwd)/../../../t
+TEST_DIRECTORY=$(pwd)/../../../t
+export TEST_DIRECTORY
. ../../../t/test-lib.sh
# Save this hash for testing later.
-subdir_hash=`git rev-parse HEAD`
+subdir_hash=$(git rev-parse HEAD)
test_expect_success 'add sub2' '
create sub2 &&
To simulate incremental imports the environment variable SVNRMAX can be set
to the highest revision that should be available.
"""
-import sys, os
+import sys
+import os
if sys.hexversion < 0x02040000:
- # The limiter is the ValueError() calls. This may be too conservative
- sys.stderr.write("svnrdump-sim.py: requires Python 2.4 or later.\n")
- sys.exit(1)
+ # The limiter is the ValueError() calls. This may be too conservative
+ sys.stderr.write("svnrdump-sim.py: requires Python 2.4 or later.\n")
+ sys.exit(1)
+
def getrevlimit():
- var = 'SVNRMAX'
- if var in os.environ:
- return os.environ[var]
- return None
+ var = 'SVNRMAX'
+ if var in os.environ:
+ return os.environ[var]
+ return None
+
def writedump(url, lower, upper):
- if url.startswith('sim://'):
- filename = url[6:]
- if filename[-1] == '/': filename = filename[:-1] #remove terminating slash
- else:
- raise ValueError('sim:// url required')
- f = open(filename, 'r');
- state = 'header'
- wroterev = False
- while(True):
- l = f.readline()
- if l == '': break
- if state == 'header' and l.startswith('Revision-number: '):
- state = 'prefix'
- if state == 'prefix' and l == 'Revision-number: %s\n' % lower:
- state = 'selection'
- if not upper == 'HEAD' and state == 'selection' and l == 'Revision-number: %s\n' % upper:
- break;
+ if url.startswith('sim://'):
+ filename = url[6:]
+ if filename[-1] == '/':
+ filename = filename[:-1] # remove terminating slash
+ else:
+ raise ValueError('sim:// url required')
+ f = open(filename, 'r')
+ state = 'header'
+ wroterev = False
+ while(True):
+ l = f.readline()
+ if l == '':
+ break
+ if state == 'header' and l.startswith('Revision-number: '):
+ state = 'prefix'
+ if state == 'prefix' and l == 'Revision-number: %s\n' % lower:
+ state = 'selection'
+ if not upper == 'HEAD' and state == 'selection' and \
+ l == 'Revision-number: %s\n' % upper:
+ break
- if state == 'header' or state == 'selection':
- if state == 'selection': wroterev = True
- sys.stdout.write(l)
- return wroterev
+ if state == 'header' or state == 'selection':
+ if state == 'selection':
+ wroterev = True
+ sys.stdout.write(l)
+ return wroterev
if __name__ == "__main__":
- if not (len(sys.argv) in (3, 4, 5)):
- print("usage: %s dump URL -rLOWER:UPPER")
- sys.exit(1)
- if not sys.argv[1] == 'dump': raise NotImplementedError('only "dump" is suppported.')
- url = sys.argv[2]
- r = ('0', 'HEAD')
- if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r':
- r = sys.argv[3][2:].lstrip().split(':')
- if not getrevlimit() is None: r[1] = getrevlimit()
- if writedump(url, r[0], r[1]): ret = 0
- else: ret = 1
- sys.exit(ret)
+ if not (len(sys.argv) in (3, 4, 5)):
+ print("usage: %s dump URL -rLOWER:UPPER")
+ sys.exit(1)
+ if not sys.argv[1] == 'dump':
+ raise NotImplementedError('only "dump" is suppported.')
+ url = sys.argv[2]
+ r = ('0', 'HEAD')
+ if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r':
+ r = sys.argv[3][2:].lstrip().split(':')
+ if not getrevlimit() is None:
+ r[1] = getrevlimit()
+ if writedump(url, r[0], r[1]):
+ ret = 0
+ else:
+ ret = 1
+ sys.exit(ret)
SEP="-=-=-=-=-=-=-=-=-=# Don't remove this line #=-=-=-=-=-=-=-=-=-"
if [ -e "$CONFFILE" ] ; then
- LAST_DIR=`grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//'`
+ LAST_DIR=$(grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//')
cd "${LAST_DIR}"
else
cd > /dev/null
cd - > /dev/null
-SUBJECT=`sed -n -e '/^Subject: /p' "${PATCH}"`
-HEADERS=`sed -e '/^'"${SEP}"'$/,$d' $1`
-BODY=`sed -e "1,/${SEP}/d" $1`
-CMT_MSG=`sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}"`
-DIFF=`sed -e '1,/^---$/d' "${PATCH}"`
+SUBJECT=$(sed -n -e '/^Subject: /p' "${PATCH}")
+HEADERS=$(sed -e '/^'"${SEP}"'$/,$d' $1)
+BODY=$(sed -e "1,/${SEP}/d" $1)
+CMT_MSG=$(sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}")
+DIFF=$(sed -e '1,/^---$/d' "${PATCH}")
CCS=`echo -e "$CMT_MSG\n$HEADERS" | sed -n -e 's/^Cc: \(.*\)$/\1,/gp' \
-e 's/^Signed-off-by: \(.*\)/\1,/gp'`
fi
echo "$DIFF" >> $1
-LAST_DIR=`dirname "${PATCH}"`
+LAST_DIR=$(dirname "${PATCH}")
grep -v "^LAST_DIR=" "${CONFFILE}" > "${CONFFILE}_"
echo "LAST_DIR=${LAST_DIR}" >> "${CONFFILE}_"
+++ /dev/null
-Syntax highlighting for git commit messages, config files, etc. is
-included with the vim distribution as of vim 7.2, and should work
-automatically.
-
-If you have an older version of vim, you can get the latest syntax
-files from the vim project:
-
- http://ftp.vim.org/pub/vim/runtime/syntax/git.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim
-
-These files are also available via FTP at the same location.
-
-To install:
-
- 1. Copy these files to vim's syntax directory $HOME/.vim/syntax
- 2. To auto-detect the editing of various git-related filetypes:
-
- $ curl http://ftp.vim.org/pub/vim/runtime/filetype.vim |
- sed -ne '/^" Git$/, /^$/ p' >>$HOME/.vim/filetype.vim
diff_unmerged_stage = 2;
entries = active_nr;
for (i = 0; i < entries; i++) {
- struct stat st;
unsigned int oldmode, newmode;
struct cache_entry *ce = active_cache[i];
int changed;
unsigned int wt_mode = 0;
int num_compare_stages = 0;
size_t path_len;
+ struct stat st;
path_len = ce_namelen(ce);
continue;
/* If CE_VALID is set, don't look at workdir for file removal */
- changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st);
- if (changed) {
- if (changed < 0) {
- perror(ce->name);
+ if (ce->ce_flags & CE_VALID) {
+ changed = 0;
+ newmode = ce->ce_mode;
+ } else {
+ struct stat st;
+
+ changed = check_removed(ce, &st);
+ if (changed) {
+ if (changed < 0) {
+ perror(ce->name);
+ continue;
+ }
+ diff_addremove(&revs->diffopt, '-', ce->ce_mode,
+ ce->sha1, !is_null_sha1(ce->sha1),
+ ce->name, 0);
continue;
}
- diff_addremove(&revs->diffopt, '-', ce->ce_mode,
- ce->sha1, !is_null_sha1(ce->sha1),
- ce->name, 0);
- continue;
+
+ changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
+ ce_option, &dirty_submodule);
+ newmode = ce_mode_from_stat(ce, st.st_mode);
}
- changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
- ce_option, &dirty_submodule);
+
if (!changed && !dirty_submodule) {
ce_mark_uptodate(ce);
if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
continue;
}
oldmode = ce->ce_mode;
- newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
!is_null_sha1(ce->sha1), (changed ? 0 : !is_null_sha1(ce->sha1)),
return temp;
}
+static void add_external_diff_name(struct argv_array *argv,
+ const char *name,
+ struct diff_filespec *df)
+{
+ struct diff_tempfile *temp = prepare_temp_file(name, df);
+ argv_array_push(argv, temp->name);
+ argv_array_push(argv, temp->hex);
+ argv_array_push(argv, temp->mode);
+}
+
/* An external diff command takes:
*
* diff-cmd name infile1 infile1-sha1 infile1-mode \
struct diff_options *o)
{
struct argv_array argv = ARGV_ARRAY_INIT;
- int retval;
+ struct argv_array env = ARGV_ARRAY_INIT;
struct diff_queue_struct *q = &diff_queued_diff;
- const char *env[3] = { NULL };
- char env_counter[50];
- char env_total[50];
+
+ argv_array_push(&argv, pgm);
+ argv_array_push(&argv, name);
if (one && two) {
- struct diff_tempfile *temp_one, *temp_two;
- const char *othername = (other ? other : name);
- temp_one = prepare_temp_file(name, one);
- temp_two = prepare_temp_file(othername, two);
- argv_array_push(&argv, pgm);
- argv_array_push(&argv, name);
- argv_array_push(&argv, temp_one->name);
- argv_array_push(&argv, temp_one->hex);
- argv_array_push(&argv, temp_one->mode);
- argv_array_push(&argv, temp_two->name);
- argv_array_push(&argv, temp_two->hex);
- argv_array_push(&argv, temp_two->mode);
- if (other) {
+ add_external_diff_name(&argv, name, one);
+ if (!other)
+ add_external_diff_name(&argv, name, two);
+ else {
+ add_external_diff_name(&argv, other, two);
argv_array_push(&argv, other);
argv_array_push(&argv, xfrm_msg);
}
- } else {
- argv_array_push(&argv, pgm);
- argv_array_push(&argv, name);
}
- fflush(NULL);
- env[0] = env_counter;
- snprintf(env_counter, sizeof(env_counter), "GIT_DIFF_PATH_COUNTER=%d",
- ++o->diff_path_counter);
- env[1] = env_total;
- snprintf(env_total, sizeof(env_total), "GIT_DIFF_PATH_TOTAL=%d", q->nr);
+ argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
+ argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
+
+ if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv))
+ die(_("external diff died, stopping at %s"), name);
- retval = run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env);
remove_tempfile();
argv_array_clear(&argv);
- if (retval) {
- fprintf(stderr, "external diff died, stopping at %s.\n", name);
- exit(1);
- }
+ argv_array_clear(&env);
}
static int similarity_index(struct diff_filepair *p)
options->context = diff_context_default;
DIFF_OPT_SET(options, RENAME_EMPTY);
+ /* pathchange left =NULL by default */
options->change = diff_change;
options->add_remove = diff_addremove;
options->use_color = diff_use_color_default;
}
options->diff_path_counter = 0;
+
+ if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1)
+ die(_("--follow requires exactly one pathspec"));
}
static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
void diffcore_std(struct diff_options *options)
{
+ /* NOTE please keep the following in sync with diff_tree_combined() */
if (options->skip_stat_unmatch)
diffcore_skip_stat_unmatch(options);
if (!options->found_follow) {
struct userdiff_driver;
struct sha1_array;
struct commit;
+struct combine_diff_path;
+
+typedef int (*pathchange_fn_t)(struct diff_options *options,
+ struct combine_diff_path *path);
typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
int close_file;
struct pathspec pathspec;
+ pathchange_fn_t pathchange;
change_fn_t change;
add_remove_fn_t add_remove;
diff_format_fn_t format_callback;
extern const char mime_boundary_leader[];
-extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
- const char *base, struct diff_options *opt);
+extern struct combine_diff_path *diff_tree_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parent_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt);
extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
const char *base, struct diff_options *opt);
extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
static void trim_trailing_spaces(char *buf)
{
- int i, last_space = -1, nr_spaces, len = strlen(buf);
- for (i = 0; i < len; i++)
- if (buf[i] == '\\')
- i++;
- else if (buf[i] == ' ') {
- if (last_space == -1) {
- last_space = i;
- nr_spaces = 1;
- } else
- nr_spaces++;
- } else
- last_space = -1;
-
- if (last_space != -1 && last_space + nr_spaces == len)
- buf[last_space] = '\0';
+ char *p, *last_space = NULL;
+
+ for (p = buf; *p; p++)
+ switch (*p) {
+ case ' ':
+ if (!last_space)
+ last_space = p;
+ break;
+ case '\\':
+ p++;
+ if (!*p)
+ return;
+ /* fallthrough */
+ default:
+ last_space = NULL;
+ }
+
+ if (last_space)
+ *last_space = '\0';
}
int add_excludes_from_file_to_list(const char *fname,
int fsync_object_files;
size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
-size_t delta_base_cache_limit = 16 * 1024 * 1024;
+size_t delta_base_cache_limit = 96 * 1024 * 1024;
unsigned long big_file_threshold = 512 * 1024 * 1024;
const char *pager_program;
int pager_use_color = 1;
* that is subject to stripspace.
*/
char comment_line_char = '#';
+int auto_comment_line_char;
/* Parallel index stat data preload? */
int core_preload_index = 0;
#define has_dos_drive_prefix(path) 0
#endif
+#ifndef offset_1st_component
+#define offset_1st_component(path) (is_dir_sep((path)[0]))
+#endif
+
#ifndef is_dir_sep
#define is_dir_sep(c) ((c) == '/')
#endif
* trying to help gcc, anyway, it's OK; other compilers will fall back to
* using the function as usual.
*/
-#if defined(__GNUC__) && ! defined(__clang__)
-#define error(...) (error(__VA_ARGS__), -1)
+#if defined(__GNUC__)
+static inline int const_error(void)
+{
+ return -1;
+}
+#define error(...) (error(__VA_ARGS__), const_error())
#endif
extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
typedef void (*try_to_free_t)(size_t);
extern try_to_free_t set_try_to_free_routine(try_to_free_t);
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# define xalloca(size) (alloca(size))
+# define xalloca_free(p) do {} while (0)
+#else
+# define xalloca(size) (xmalloc(size))
+# define xalloca_free(p) (free(p))
+#endif
extern char *xstrdup(const char *str);
extern void *xmalloc(size_t size);
extern void *xmallocz(size_t size);
extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
extern ssize_t xread(int fd, void *buf, size_t len);
extern ssize_t xwrite(int fd, const void *buf, size_t len);
+extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
extern int xdup(int fd);
extern FILE *xfdopen(int fd, const char *mode);
extern int xmkstemp(char *template);
apply_config
# v1.7.0 introduced --show-toplevel to return the canonical work-tree
-if {[package vsatisfies $_git_version 1.7.0]} {
+if {[package vsatisfies $_git_version 1.7.0-]} {
if { [is_Cygwin] } {
catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]}
} else {
close $fd
}
- if {[package vsatisfies $::_git_version 1.6.3]} {
+ if {[package vsatisfies $::_git_version 1.6.3-]} {
set ls_others [list --exclude-standard]
} else {
set ls_others [list --exclude-per-directory=.gitignore]
echo "Normal merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
- if "$prompt" = true
+ if test "$guessed_merge_tool" = true || test "$prompt" = true
then
printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
read ans || return 1
return 0
}
-prompt=$(git config --bool mergetool.prompt || echo true)
+prompt=$(git config --bool mergetool.prompt)
+guessed_merge_tool=false
while test $# != 0
do
if test -z "$merge_tool"
then
- merge_tool=$(get_merge_tool "$merge_tool") || exit
+ # Check if a merge tool has been configured
+ merge_tool=$(get_configured_merge_tool)
+ # Try to guess an appropriate merge tool if no tool has been set.
+ if test -z "$merge_tool"
+ then
+ merge_tool=$(guess_merge_tool) || exit
+ guessed_merge_tool=true
+ fi
fi
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
if response == 'n':
return False
+ def get_diff_description(self, editedFiles):
+ # diff
+ if os.environ.has_key("P4DIFF"):
+ del(os.environ["P4DIFF"])
+ diff = ""
+ for editedFile in editedFiles:
+ diff += p4_read_pipe(['diff', '-du',
+ wildcard_encode(editedFile)])
+
+ # new file diff
+ 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()
+
+ return diff + newdiff
+
def applyCommit(self, id):
"""Apply one commit, return True if it succeeded."""
submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
separatorLine = "######## everything below this line is just the diff #######\n"
+ if not self.prepare_p4_only:
+ submitTemplate += separatorLine
+ submitTemplate += self.get_diff_description(editedFiles)
- # diff
- if os.environ.has_key("P4DIFF"):
- del(os.environ["P4DIFF"])
- diff = ""
- for editedFile in editedFiles:
- diff += p4_read_pipe(['diff', '-du',
- wildcard_encode(editedFile)])
-
- # new file diff
- 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()
-
- # change description file: submitTemplate, separatorLine, diff, newdiff
(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.write(submitTemplate)
tmpFile.close()
if self.prepare_p4_only:
case "$pull_ff" in
false)
no_ff=--no-ff
- break
;;
only)
ff_only=--ff-only
- break
;;
esac
-s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
case "$#,$1" in
*,*=*)
- strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
+ strategy=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
1,*)
usage ;;
*)
has_action "$todo" ||
- die_abort "Nothing to do"
+ return 2
cp "$todo" "$todo".backup
git_sequence_editor "$todo" ||
die_abort "Could not execute editor"
has_action "$todo" ||
- die_abort "Nothing to do"
+ return 2
expand_todo_ids
die "$resolvemsg"
fi
- cmt=`cat "$state_dir/current"`
+ cmt=$(cat "$state_dir/current")
if ! git diff-index --quiet --ignore-submodules HEAD --
then
if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} --no-verify -C "$cmt"
write_basic_state
msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$revisions"`
+for cmt in $(git rev-list --reverse --no-merges "$revisions")
do
msgnum=$(($msgnum + 1))
echo "$cmt" > "$state_dir/cmt.$msgnum"
esac
}
-finish_rebase () {
+apply_autostash () {
if test -f "$state_dir/autostash"
then
stash_sha1=$(cat "$state_dir/autostash")
'
fi
fi
+}
+
+finish_rebase () {
+ apply_autostash &&
git gc --auto &&
rm -rf "$state_dir"
}
if test $ret -eq 0
then
finish_rebase
+ elif test $ret -eq 2 # special exit status for rebase -i
+ then
+ apply_autostash &&
+ rm -rf "$state_dir" &&
+ die "Nothing to do"
fi
exit $ret
}
else
if test -z "$onto"
then
- empty_tree=`git hash-object -t tree /dev/null`
- onto=`git commit-tree $empty_tree </dev/null`
+ empty_tree=$(git hash-object -t tree /dev/null)
+ onto=$(git commit-tree $empty_tree </dev/null)
squash_onto="$onto"
fi
unset upstream_name
;;
0)
# Do not need to switch branches, we are already on it.
- if branch_name=`git symbolic-ref -q HEAD`
+ if branch_name=$(git symbolic-ref -q HEAD)
then
head_name=$branch_name
- branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+ branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)')
else
head_name="detached HEAD"
branch_name=HEAD ;# detached
test -z "$refspec" && prefix="refs"
-export GIT_DIR="$url/.git"
+GIT_DIR="$url/.git"
+export GIT_DIR
force=
status=1
fi
+# Special case: turn "for_linus" to "tags/for_linus" when it is correct
+if test "$ref" = "refs/tags/$pretty_remote"
+then
+ pretty_remote=tags/$pretty_remote
+fi
+
url=$(git ls-remote --get-url "$url")
git show -s --format='The following changes since commit %H:
}
}
+sub file_name_is_absolute {
+ my ($path) = @_;
+
+ # msys does not grok DOS drive-prefixes
+ if ($^O eq 'msys') {
+ return ($path =~ m#^/# || $path =~ m#^[a-zA-Z]\:#)
+ }
+
+ require File::Spec::Functions;
+ return File::Spec::Functions::file_name_is_absolute($path);
+}
+
# Returns 1 if the message was sent, and 0 otherwise.
# In actuality, the whole program dies when there
# is an error sending a message.
if ($dry_run) {
# We don't want to send the email.
- } elsif ($smtp_server =~ m#^/#) {
+ } elsif (file_name_is_absolute($smtp_server)) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
} else {
print (($dry_run ? "Dry-" : "")."OK. Log says:\n");
- if ($smtp_server !~ m#^/#) {
+ if (!file_name_is_absolute($smtp_server)) {
print "Server: $smtp_server\n";
print "MAIL FROM:<$raw_from>\n";
foreach my $entry (@recipients) {
else
GIT_PAGER=cat
fi
- : ${LESS=-FRSX}
+ : ${LESS=-FRX}
: ${LV=-c}
export LESS LV
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
-START_DIR=`pwd`
+START_DIR=$(pwd)
. git-sh-setup
. git-sh-i18n
require_work_tree
# ease of unpacking later.
u_commit=$(
untracked_files | (
- export GIT_INDEX_FILE="$TMPindex"
+ GIT_INDEX_FILE="$TMPindex" &&
+ export GIT_INDEX_FILE &&
rm -f "$TMPindex" &&
git update-index -z --add --remove --stdin &&
u_tree=$(git write-tree) &&
# resolve any symlinks that might be present in $PWD
a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
b=$(cd_to_toplevel && 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
# Remove all common leading directories after a sanity check
if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then
die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")"
-b|--browser*|-t|--tool*)
case "$#,$1" in
*,*=*)
- browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ browser=$(expr "z$1" : 'z-[^=]*=\(.*\)')
;;
1,*)
usage ;;
-c|--config*)
case "$#,$1" in
*,*=*)
- conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ conf=$(expr "z$1" : 'z-[^=]*=\(.*\)')
;;
1,*)
usage ;;
for opt in "$conf" "web.browser"
do
test -z "$opt" && continue
- browser="`git config $opt`"
+ browser="$(git config $opt)"
test -z "$browser" || break
done
if test -n "$browser" && ! valid_tool "$browser"; then
if (!help) {
if (p->option & RUN_SETUP)
prefix = setup_git_directory();
- if (p->option & RUN_SETUP_GENTLY) {
+ else if (p->option & RUN_SETUP_GENTLY) {
int nongit_ok;
prefix = setup_git_directory_gently(&nongit_ok);
}
return 0;
}
+ if (!strcmp(var, "grep.fullname")) {
+ opt->relative = !git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "color.grep"))
opt->color = git_config_colorbool(var, value);
else if (!strcmp(var, "color.grep.context"))
*/
#include "cache.h"
+#include "credential.h"
#include "exec_cmd.h"
#include "run-command.h"
-#include "prompt.h"
#ifdef NO_OPENSSL
typedef void *SSL;
#endif
static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
{
+ struct credential cred = CREDENTIAL_INIT;
struct imap_store *ctx;
struct imap *imap;
char *arg, *rsp;
}
#endif
imap_info("Logging in...\n");
- if (!srvc->user) {
- fprintf(stderr, "Skipping server %s, no user\n", srvc->host);
- goto bail;
- }
- if (!srvc->pass) {
- struct strbuf prompt = STRBUF_INIT;
- strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
- arg = git_getpass(prompt.buf);
- strbuf_release(&prompt);
- if (!*arg) {
- fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
- goto bail;
- }
- /*
- * getpass() returns a pointer to a static buffer. make a copy
- * for long term storage.
- */
- srvc->pass = xstrdup(arg);
+ if (!srvc->user || !srvc->pass) {
+ cred.protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap");
+ cred.host = xstrdup(srvc->host);
+
+ if (srvc->user)
+ cred.username = xstrdup(srvc->user);
+ if (srvc->pass)
+ cred.password = xstrdup(srvc->pass);
+
+ credential_fill(&cred);
+
+ if (!srvc->user)
+ srvc->user = xstrdup(cred.username);
+ if (!srvc->pass)
+ srvc->pass = xstrdup(cred.password);
}
+
if (CAP(NOLOGIN)) {
fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
goto bail;
}
} /* !preauth */
+ if (cred.username)
+ credential_approve(&cred);
+ credential_clear(&cred);
+
ctx->prefix = "";
return ctx;
bail:
+ if (cred.username)
+ credential_reject(&cred);
+ credential_clear(&cred);
+
imap_close_store(ctx);
return NULL;
}
return -1;
}
if (update_working_directory) {
+ if (ignore_case) {
+ struct cache_entry *ce;
+ ce = cache_file_exists(path, strlen(path), ignore_case);
+ if (ce && ce_stage(ce) == 0)
+ return 0;
+ }
if (remove_path(path))
return -1;
}
--- /dev/null
+. "$MERGE_TOOLS_DIR/vimdiff"
"$merge_tool_path" -f -d -c 'wincmd l' \
"$LOCAL" "$MERGED" "$REMOTE"
;;
+ gvimdiff3|vimdiff3)
+ if $base_present
+ then
+ "$merge_tool_path" -f -d -c 'hid | hid | hid' \
+ "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
+ else
+ "$merge_tool_path" -f -d -c 'hid | hid' \
+ "$LOCAL" "$REMOTE" "$MERGED"
+ fi
+ ;;
esac
check_unchanged
}
translate_merge_tool_path() {
case "$1" in
- gvimdiff|gvimdiff2)
+ gvimdiff|gvimdiff2|gvimdiff3)
echo gvim
;;
- vimdiff|vimdiff2)
+ vimdiff|vimdiff2|vimdiff3)
echo vim
;;
esac
--- /dev/null
+. "$MERGE_TOOLS_DIR/vimdiff"
if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
return -1;
if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
- 0, QUIET_ON_ERR) < 0)
+ 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
return -1;
return 0;
create_notes_commit(t, NULL, &buf, commit_sha1);
strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
- update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
+ update_ref(buf.buf, t->ref, commit_sha1, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
strbuf_release(&buf);
}
{
const char *pager = git_pager(isatty(1));
- if (!pager || pager_in_use())
+ if (!pager)
return;
/*
int i = 0;
if (!getenv("LESS"))
- env[i++] = "LESS=FRSX";
+ env[i++] = "LESS=FRX";
if (!getenv("LV"))
env[i++] = "LV=-c";
env[i] = NULL;
extern int optbug(const struct option *opt, const char *reason);
extern int opterror(const struct option *opt, const char *reason, int flags);
-#if defined(__GNUC__) && ! defined(__clang__)
-#define opterror(o,r,f) (opterror((o),(r),(f)), -1)
+#if defined(__GNUC__)
+#define opterror(o,r,f) (opterror((o),(r),(f)), const_error())
#endif
/*----- incremental advanced APIs -----*/
}
}
}
-
-int offset_1st_component(const char *path)
-{
- if (has_dos_drive_prefix(path))
- return 2 + is_dir_sep(path[2]);
- return is_dir_sep(path[0]);
-}
sub parse_svn_date {
my $date = shift || return '+0000 1970-01-01 00:00:00';
my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
- (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or
+ (\d\d?)\:(\d\d)\:(\d\d)\.\d*Z$/x) or
croak "Unable to parse date: $date\n";
my $parsed_date; # Set next.
return;
}
open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!";
- $ENV{LESS} ||= 'FRSX';
+ $ENV{LESS} ||= 'FRX';
$ENV{LV} ||= '-c';
exec $pager or fatal "Can't run pager: $! ($pager)";
}
msgstr ""
"\n"
"Si vous comptez baser votre travail sur une branche\n"
-"amont qui existe déjà sur le serveur distant, vous pourriez\n"
-"devoir lancer \"git fetch\" pour la récupérer.\n"
+"amont qui existe déjà sur le serveur distant, vous pouvez\n"
+"lancer \"git fetch\" pour la récupérer.\n"
"\n"
"Si vous comptez pousser une nouvelle branche locale qui suivra\n"
-"sa jumelle distante, vous souhaiterez utiliser \"git push -u\"\n"
+"sa jumelle distante, vous pouvez utiliser \"git push -u\"\n"
"pour paramétrer le suivi distant en même temps que vous poussez."
#: branch.c:260
#: bundle.c:138
msgid "Repository lacks these prerequisite commits:"
-msgstr "Le dépôt ne dispose pas des commits prérequis :"
+msgstr "Le dépôt ne dispose pas des commits prérequis suivants :"
#: bundle.c:162 sequencer.c:669 sequencer.c:1123 builtin/log.c:332
#: builtin/log.c:821 builtin/log.c:1418 builtin/log.c:1644 builtin/merge.c:357
msgstr ""
"ATTENTION : vous avez invoqué une commande Git nommée '%s' qui n'existe "
"pas.\n"
-"Poursuite en supposant que vous avez voulu dire '%s'"
+"Continuons en supposant que vous avez voulu dire '%s'"
#: help.c:373
#, c-format
"\n"
"%s\n"
msgstr[0] ""
-"Attention : vous laissez %d commit en retard, non connectés à\n"
+"Attention : vous abandonnez %d commit, non connecté à\n"
"une branche :\n"
"\n"
"%s\n"
msgstr[1] ""
-"Attention : vous laissez %d commits en retard, non connectés à\n"
+"Attention : vous abandonnez %d commits, non connectés à\n"
"une branche :\n"
"\n"
"%s\n"
msgstr ""
"Si vous souhaitez les garder en créant une nouvelle branche, c'est le bon "
"moment\n"
-"de le faire avec :\n"
+"pour le faire avec :\n"
"\n"
"git branch nouvelle_branche %s\n"
"\n"
#: builtin/checkout.c:1096
msgid "set upstream info for new branch"
-msgstr "paramétrer l'information de branche amont pour une nouvelle branche"
+msgstr "paramétrer les coordonnées de branche amont pour une nouvelle branche"
#: builtin/checkout.c:1098
msgid "new-branch"
#: builtin/clone.c:64 builtin/fetch.c:97 builtin/merge.c:222
#: builtin/push.c:504
msgid "force progress reporting"
-msgstr "forcer l'état d'avancement"
+msgstr "forcer l'affichage de l'état d'avancement"
#: builtin/clone.c:66
msgid "don't create a checkout"
msgstr ""
"Votre nom et votre adresse e-mail ont été configurés automatiquement en se "
"fondant\n"
-"sur votre nom d'utilisateur et votre nom d'ordinateur. Veuillez vérifier "
+"sur votre nom d'utilisateur et le nom de votre machine. Veuillez vérifier "
"qu'ils sont corrects.\n"
"Vous pouvez supprimer ce message en les paramétrant explicitement :\n"
"\n"
#: builtin/commit.c:1496
msgid "use autosquash formatted message to fixup specified commit"
msgstr ""
-"utiliser un message formaté par autosquash pour corriger le commit spécifié"
+"utiliser un message au format autosquash pour corriger le commit spécifié"
#: builtin/commit.c:1497
msgid "use autosquash formatted message to squash specified commit"
msgstr ""
-"utiliser un message formaté par autosquash pour compresser le commit spécifié"
+"utiliser un message au format autosquash pour compresser le commit spécifié"
#: builtin/commit.c:1498
msgid "the commit is authored by me now (used with -C/-c/--amend)"
#: builtin/log.c:809
msgid "Not a range."
-msgstr "Pas une plage."
+msgstr "Ceci n'est pas une plage."
#: builtin/log.c:911
msgid "Cover letter needs email format"
#: builtin/log.c:1221 builtin/log.c:1224
msgid "boundary"
-msgstr "frontière"
+msgstr "limite"
#: builtin/log.c:1222
msgid "attach the patch"
#: builtin/push.c:361
#, c-format
msgid "Pushing to %s\n"
-msgstr "Poussage vers %s\n"
+msgstr "Poussée vers %s\n"
#: builtin/push.c:365
#, c-format
#: builtin/push.c:497
msgid "control recursive pushing of submodules"
-msgstr "contrôler le poussage récursif des sous-modules"
+msgstr "contrôler la poussée récursive des sous-modules"
#: builtin/push.c:499
msgid "use thin pack"
#: builtin/remote.c:1307
#, c-format
msgid " %s will become dangling!"
-msgstr " %s deviendra en suspens !"
+msgstr " %s se retrouvera en suspens !"
#: builtin/remote.c:1308
#, c-format
msgid " %s has become dangling!"
-msgstr " %s est devenu en suspens !"
+msgstr " %s se retrouve en suspens !"
#: builtin/remote.c:1314
#, c-format
#: git-stash.sh:457
msgid "Could not save index tree"
-msgstr "Impossible de sauver l'arbre d'index"
+msgstr "Impossible de sauvegarder l'arbre d'index"
#: git-stash.sh:491
msgid "Cannot unstage modified files"
#, sh-format
msgid "No url found for submodule path '$displaypath' in .gitmodules"
msgstr ""
-"URL non trouvé pour le chemin de sous-module '$displaypath' dans .gitmodules"
+"URL non trouvée pour le chemin de sous-module '$displaypath' dans .gitmodules"
#: git-submodule.sh:620
#, sh-format
strbuf_addstr(sb, "?=");
}
-static const char *show_ident_date(const struct ident_split *ident,
- enum date_mode mode)
+const char *show_ident_date(const struct ident_split *ident,
+ enum date_mode mode)
{
unsigned long date = 0;
long tz = 0;
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
+ hashcpy(istate->sha1, (unsigned char *)hdr + mmap_size - 20);
istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
return result;
}
+/*
+ * This function verifies if index_state has the correct sha1 of the
+ * index file. Don't die if we have any other failure, just return 0.
+ */
+static int verify_index_from(const struct index_state *istate, const char *path)
+{
+ int fd;
+ ssize_t n;
+ struct stat st;
+ unsigned char sha1[20];
+
+ if (!istate->initialized)
+ return 0;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ if (fstat(fd, &st))
+ goto out;
+
+ if (st.st_size < sizeof(struct cache_header) + 20)
+ goto out;
+
+ n = pread_in_full(fd, sha1, 20, st.st_size - 20);
+ if (n != 20)
+ goto out;
+
+ if (hashcmp(istate->sha1, sha1))
+ goto out;
+
+ close(fd);
+ return 1;
+
+out:
+ close(fd);
+ return 0;
+}
+
+static int verify_index(const struct index_state *istate)
+{
+ return verify_index_from(istate, get_index_file());
+}
+
static int has_racy_timestamp(struct index_state *istate)
{
int entries = istate->cache_nr;
void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
{
if ((istate->cache_changed || has_racy_timestamp(istate)) &&
- !write_index(istate, lockfile->fd))
+ verify_index(istate) && !write_index(istate, lockfile->fd))
commit_locked_index(lockfile);
else
rollback_lock_file(lockfile);
*log = NULL;
for (p = ref_rev_parse_rules; *p; p++) {
- struct stat st;
unsigned char hash[20];
char path[PATH_MAX];
const char *ref, *it;
ref = resolve_ref_unsafe(path, hash, 1, NULL);
if (!ref)
continue;
- if (!stat(git_path("logs/%s", path), &st) &&
- S_ISREG(st.st_mode))
+ if (reflog_exists(path))
it = path;
- else if (strcmp(ref, path) &&
- !stat(git_path("logs/%s", ref), &st) &&
- S_ISREG(st.st_mode))
+ else if (strcmp(ref, path) && reflog_exists(ref))
it = ref;
else
continue;
return 1;
}
+int reflog_exists(const char *refname)
+{
+ struct stat st;
+
+ return !lstat(git_path("logs/%s", refname), &st) &&
+ S_ISREG(st.st_mode);
+}
+
+int delete_reflog(const char *refname)
+{
+ return remove_path(git_path("logs/%s", refname));
+}
+
static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
{
unsigned char osha1[20], nsha1[20];
if (!lock) {
const char *str = "Cannot lock the ref '%s'.";
switch (onerr) {
- case MSG_ON_ERR: error(str, refname); break;
- case DIE_ON_ERR: die(str, refname); break;
- case QUIET_ON_ERR: break;
+ case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+ case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR: break;
}
}
return lock;
if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'.";
switch (onerr) {
- case MSG_ON_ERR: error(str, refname); break;
- case DIE_ON_ERR: die(str, refname); break;
- case QUIET_ON_ERR: break;
+ case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+ case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR: break;
}
return 1;
}
return 0;
}
+/**
+ * Information needed for a single ref update. Set new_sha1 to the
+ * new value or to zero to delete the ref. To check the old value
+ * while locking the ref, set have_old to 1 and set old_sha1 to the
+ * value or to zero to ensure the ref does not exist before update.
+ */
+struct ref_update {
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int flags; /* REF_NODEREF? */
+ int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
+ struct ref_lock *lock;
+ int type;
+ const char refname[FLEX_ARRAY];
+};
+
+/*
+ * Data structure for holding a reference transaction, which can
+ * consist of checks and updates to multiple references, carried out
+ * as atomically as possible. This structure is opaque to callers.
+ */
+struct ref_transaction {
+ struct ref_update **updates;
+ size_t alloc;
+ size_t nr;
+};
+
+struct ref_transaction *ref_transaction_begin(void)
+{
+ return xcalloc(1, sizeof(struct ref_transaction));
+}
+
+static void ref_transaction_free(struct ref_transaction *transaction)
+{
+ int i;
+
+ for (i = 0; i < transaction->nr; i++)
+ free(transaction->updates[i]);
+
+ free(transaction->updates);
+ free(transaction);
+}
+
+void ref_transaction_rollback(struct ref_transaction *transaction)
+{
+ ref_transaction_free(transaction);
+}
+
+static struct ref_update *add_update(struct ref_transaction *transaction,
+ const char *refname)
+{
+ size_t len = strlen(refname);
+ struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+
+ strcpy((char *)update->refname, refname);
+ ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
+ transaction->updates[transaction->nr++] = update;
+ return update;
+}
+
+void ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1, unsigned char *old_sha1,
+ int flags, int have_old)
+{
+ struct ref_update *update = add_update(transaction, refname);
+
+ hashcpy(update->new_sha1, new_sha1);
+ update->flags = flags;
+ update->have_old = have_old;
+ if (have_old)
+ hashcpy(update->old_sha1, old_sha1);
+}
+
+void ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1,
+ int flags)
+{
+ struct ref_update *update = add_update(transaction, refname);
+
+ assert(!is_null_sha1(new_sha1));
+ hashcpy(update->new_sha1, new_sha1);
+ hashclr(update->old_sha1);
+ update->flags = flags;
+ update->have_old = 1;
+}
+
+void ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *old_sha1,
+ int flags, int have_old)
+{
+ struct ref_update *update = add_update(transaction, refname);
+
+ update->flags = flags;
+ update->have_old = have_old;
+ if (have_old) {
+ assert(!is_null_sha1(old_sha1));
+ hashcpy(update->old_sha1, old_sha1);
+ }
+}
+
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
const struct ref_update * const *u1 = r1;
const struct ref_update * const *u2 = r2;
- return strcmp((*u1)->ref_name, (*u2)->ref_name);
+ return strcmp((*u1)->refname, (*u2)->refname);
}
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
{
int i;
for (i = 1; i < n; i++)
- if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+ if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
const char *str =
"Multiple updates for ref '%s' not allowed.";
switch (onerr) {
- case MSG_ON_ERR:
- error(str, updates[i]->ref_name); break;
- case DIE_ON_ERR:
- die(str, updates[i]->ref_name); break;
- case QUIET_ON_ERR:
+ case UPDATE_REFS_MSG_ON_ERR:
+ error(str, updates[i]->refname); break;
+ case UPDATE_REFS_DIE_ON_ERR:
+ die(str, updates[i]->refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR:
break;
}
return 1;
return 0;
}
-int update_refs(const char *action, const struct ref_update **updates_orig,
- int n, enum action_on_err onerr)
+int ref_transaction_commit(struct ref_transaction *transaction,
+ const char *msg, enum action_on_err onerr)
{
int ret = 0, delnum = 0, i;
- struct ref_update **updates;
- int *types;
- struct ref_lock **locks;
const char **delnames;
+ int n = transaction->nr;
+ struct ref_update **updates = transaction->updates;
- if (!updates_orig || !n)
+ if (!n)
return 0;
/* Allocate work space */
- updates = xmalloc(sizeof(*updates) * n);
- types = xmalloc(sizeof(*types) * n);
- locks = xcalloc(n, sizeof(*locks));
delnames = xmalloc(sizeof(*delnames) * n);
/* Copy, sort, and reject duplicate refs */
- memcpy(updates, updates_orig, sizeof(*updates) * n);
qsort(updates, n, sizeof(*updates), ref_update_compare);
ret = ref_update_reject_duplicates(updates, n, onerr);
if (ret)
/* Acquire all locks while verifying old values */
for (i = 0; i < n; i++) {
- locks[i] = update_ref_lock(updates[i]->ref_name,
- (updates[i]->have_old ?
- updates[i]->old_sha1 : NULL),
- updates[i]->flags,
- &types[i], onerr);
- if (!locks[i]) {
+ struct ref_update *update = updates[i];
+
+ update->lock = update_ref_lock(update->refname,
+ (update->have_old ?
+ update->old_sha1 : NULL),
+ update->flags,
+ &update->type, onerr);
+ if (!update->lock) {
ret = 1;
goto cleanup;
}
}
/* Perform updates first so live commits remain referenced */
- for (i = 0; i < n; i++)
- if (!is_null_sha1(updates[i]->new_sha1)) {
- ret = update_ref_write(action,
- updates[i]->ref_name,
- updates[i]->new_sha1,
- locks[i], onerr);
- locks[i] = NULL; /* freed by update_ref_write */
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if (!is_null_sha1(update->new_sha1)) {
+ ret = update_ref_write(msg,
+ update->refname,
+ update->new_sha1,
+ update->lock, onerr);
+ update->lock = NULL; /* freed by update_ref_write */
if (ret)
goto cleanup;
}
+ }
/* Perform deletes now that updates are safely completed */
- for (i = 0; i < n; i++)
- if (locks[i]) {
- delnames[delnum++] = locks[i]->ref_name;
- ret |= delete_ref_loose(locks[i], types[i]);
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if (update->lock) {
+ delnames[delnum++] = update->lock->ref_name;
+ ret |= delete_ref_loose(update->lock, update->type);
}
+ }
+
ret |= repack_without_refs(delnames, delnum);
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
cleanup:
for (i = 0; i < n; i++)
- if (locks[i])
- unlock_ref(locks[i]);
- free(updates);
- free(types);
- free(locks);
+ if (updates[i]->lock)
+ unlock_ref(updates[i]->lock);
free(delnames);
+ ref_transaction_free(transaction);
return ret;
}
int force_write;
};
-/**
- * Information needed for a single ref update. Set new_sha1 to the
- * new value or to zero to delete the ref. To check the old value
- * while locking the ref, set have_old to 1 and set old_sha1 to the
- * value or to zero to ensure the ref does not exist before update.
- */
-struct ref_update {
- const char *ref_name;
- unsigned char new_sha1[20];
- unsigned char old_sha1[20];
- int flags; /* REF_NODEREF? */
- int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
-};
+struct ref_transaction;
/*
* Bit values set in the flags argument passed to each_ref_fn():
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Setup reflog before using. **/
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+int log_ref_setup(const char *refname, char *logfile, int bufsize);
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
unsigned char *sha1, char **msg,
unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+/** Check if a particular reflog exists */
+extern int reflog_exists(const char *refname);
+
+/** Delete a reflog */
+extern int delete_reflog(const char *refname);
+
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
*/
extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
-/** lock a ref and then write its file */
-enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
+enum action_on_err {
+ UPDATE_REFS_MSG_ON_ERR,
+ UPDATE_REFS_DIE_ON_ERR,
+ UPDATE_REFS_QUIET_ON_ERR
+};
+
+/*
+ * Begin a reference transaction. The reference transaction must
+ * eventually be commited using ref_transaction_commit() or rolled
+ * back using ref_transaction_rollback().
+ */
+struct ref_transaction *ref_transaction_begin(void);
+
+/*
+ * Roll back a ref_transaction and free all associated data.
+ */
+void ref_transaction_rollback(struct ref_transaction *transaction);
+
+
+/*
+ * The following functions add a reference check or update to a
+ * ref_transaction. In all of them, refname is the name of the
+ * reference to be affected. The functions make internal copies of
+ * refname, so the caller retains ownership of the parameter. flags
+ * can be REF_NODEREF; it is passed to update_ref_lock().
+ */
+
+
+/*
+ * Add a reference update to transaction. new_sha1 is the value that
+ * the reference should have after the update, or zeros if it should
+ * be deleted. If have_old is true, then old_sha1 holds the value
+ * that the reference should have had before the update, or zeros if
+ * it must not have existed beforehand.
+ */
+void ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1, unsigned char *old_sha1,
+ int flags, int have_old);
+
+/*
+ * Add a reference creation to transaction. new_sha1 is the value
+ * that the reference should have after the update; it must not be the
+ * null SHA-1. It is verified that the reference does not exist
+ * already.
+ */
+void ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1,
+ int flags);
+
+/*
+ * Add a reference deletion to transaction. If have_old is true, then
+ * old_sha1 holds the value that the reference should have had before
+ * the update (which must not be the null SHA-1).
+ */
+void ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *old_sha1,
+ int flags, int have_old);
+
+/*
+ * Commit all of the changes that have been queued in transaction, as
+ * atomically as possible. Return a nonzero value if there is a
+ * problem. The ref_transaction is freed by this function.
+ */
+int ref_transaction_commit(struct ref_transaction *transaction,
+ const char *msg, enum action_on_err onerr);
+
+/** Lock a ref and then write its file */
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr);
-/**
- * Lock all refs and then perform all modifications.
- */
-int update_refs(const char *action, const struct ref_update **updates,
- int n, enum action_on_err onerr);
-
extern int parse_hide_refs_config(const char *var, const char *value, const char *);
extern int ref_is_hidden(const char *);
!strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
!strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
!strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
+ starts_with(arg, "--exclude=") ||
starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
{
int failed_errno;
char *str;
+ if (!cmd->argv)
+ cmd->argv = cmd->args.argv;
+
/*
* In case of errors we must keep the promise to close FDs
* that have been passed in via ->in and ->out.
fail_pipe:
error("cannot create %s pipe for %s: %s",
str, cmd->argv[0], strerror(failed_errno));
+ argv_array_clear(&cmd->args);
errno = failed_errno;
return -1;
}
close_pair(fderr);
else if (cmd->err)
close(cmd->err);
+ argv_array_clear(&cmd->args);
errno = failed_errno;
return -1;
}
int finish_command(struct child_process *cmd)
{
- return wait_or_whine(cmd->pid, cmd->argv[0]);
+ int ret = wait_or_whine(cmd->pid, cmd->argv[0]);
+ argv_array_clear(&cmd->args);
+ return ret;
}
int run_command(struct child_process *cmd)
#include <pthread.h>
#endif
+#include "argv-array.h"
+
struct child_process {
const char **argv;
+ struct argv_array args;
pid_t pid;
/*
* Using .in, .out, .err:
exit(1); /* the callee should have complained already */
ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
0, NULL);
+ if (!ref_lock)
+ return error(_("Failed to lock HEAD during fast_forward_to"));
+
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
ret = write_ref_sha1(ref_lock, to, sb.buf);
+
strbuf_release(&sb);
return ret;
}
{
int fd;
struct alternate_object_database *alt;
+ int most_interesting_errno;
fd = git_open_noatime(sha1_file_name(sha1));
if (fd >= 0)
return fd;
+ most_interesting_errno = errno;
prepare_alt_odb();
- errno = ENOENT;
for (alt = alt_odb_list; alt; alt = alt->next) {
fill_sha1_path(alt->name, sha1);
fd = git_open_noatime(alt->base);
if (fd >= 0)
return fd;
+ if (most_interesting_errno == ENOENT)
+ most_interesting_errno = errno;
}
+ errno = most_interesting_errno;
return -1;
}
memcpy(buf, PREFIX, pf);
term = getenv("TERM");
- if (term && strcmp(term, "dumb"))
+ if (isatty(2) && term && strcmp(term, "dumb"))
suffix = ANSI_SUFFIX;
else
suffix = DUMB_SUFFIX;
void strbuf_trim(struct strbuf *sb)
{
- char *b = sb->buf;
- while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
- sb->len--;
- while (sb->len > 0 && isspace(*b)) {
- b++;
- sb->len--;
- }
- memmove(sb->buf, b, sb->len);
- sb->buf[sb->len] = '\0';
+ strbuf_rtrim(sb);
+ strbuf_ltrim(sb);
}
void strbuf_rtrim(struct strbuf *sb)
{
...
'
+ - test_write_lines <lines>
+
+ Write <lines> on standard output, one line per argument.
+ Useful to prepare multi-line files in a compact form.
+
+ Example:
+
+ test_write_lines a b c d e f g >foo
+
+ Is a more compact equivalent of:
+ cat >foo <<-EOF
+ a
+ b
+ c
+ d
+ e
+ f
+ g
+ EOF
+
+
- test_pause
This command is useful for writing and debugging tests and must be
while (<>) {
chomp;
- /^\s*sed\s+-i/ and err 'sed -i is not portable';
- /^\s*echo\s+-n/ and err 'echo -n is not portable (please use printf)';
+ /\bsed\s+-i/ and err 'sed -i is not portable';
+ /\becho\s+-n/ and err 'echo -n is not portable (please use printf)';
/^\s*declare\s+/ and err 'arrays/declare not portable';
/^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)';
- /test\s+[^=]*==/ and err '"test a == b" is not portable (please use =)';
- /^\s*export\s+[^=]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)';
+ /\btest\s+[^=]*==/ and err '"test a == b" is not portable (please use =)';
+ /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)';
# this resets our $. for each file
close ARGV if eof;
}
cat >askpass <<\EOF
#!/bin/sh
echo >&2 askpass: $*
-what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z`
+what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z)
echo "askpass-$what"
EOF
chmod +x askpass
CVS="cvs -f"
export CVS
-cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+cvsps_version=$(cvsps -h 2>&1 | sed -ne 's/cvsps version //p')
case "$cvsps_version" in
2.1 | 2.2*)
;;
#!/bin/sh
-gpg_version=`gpg --version 2>&1`
+gpg_version=$(gpg --version 2>&1)
if test $? = 127; then
say "You do not seem to have gpg installed"
else
test_done
fi
+if ! test_have_prereq SANITY; then
+ test_skip_or_die $GIT_TEST_HTTPD \
+ "Cannot run httpd tests as root"
+fi
+
HTTPD_PARA=""
for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2'
test_expect_success 'repack' '
git repack -ad &&
- PACK=`ls .git/objects/pack/*.pack | head -n1` &&
+ PACK=$(ls .git/objects/pack/*.pack | head -n1) &&
test -f "$PACK" &&
export PACK
'
git init --bare --shared=0666 init-bare-shared-override &&
check_config init-bare-shared-override true unset &&
test x0666 = \
- x`git config -f init-bare-shared-override/config core.sharedRepository`
+ x$(git config -f init-bare-shared-override/config core.sharedRepository)
'
test_expect_success 'init honors global core.sharedRepository' '
test_config_global core.sharedRepository 0666 &&
git init shared-honor-global &&
test x0666 = \
- x`git config -f shared-honor-global/.git/config core.sharedRepository`
+ x$(git config -f shared-honor-global/.git/config core.sharedRepository)
'
test_expect_success 'init rejects insanely long --template' '
test_expect_success 'init with separate gitdir' '
rm -rf newdir &&
git init --separate-git-dir realgitdir newdir &&
- echo "gitdir: `pwd`/realgitdir" >expected &&
+ echo "gitdir: $(pwd)/realgitdir" >expected &&
test_cmp expected newdir/.git &&
test_path_is_dir realgitdir/refs
'
cd newdir &&
git init --separate-git-dir ../surrealgitdir
) &&
- echo "gitdir: `pwd`/surrealgitdir" >expected &&
+ echo "gitdir: $(pwd)/surrealgitdir" >expected &&
test_cmp expected newdir/.git &&
test_path_is_dir surrealgitdir/refs &&
test_path_is_missing realgitdir/refs
cd newdir &&
git init --separate-git-dir ../realgitdir
) &&
- echo "gitdir: `pwd`/realgitdir" >expected &&
+ echo "gitdir: $(pwd)/realgitdir" >expected &&
test_cmp expected newdir/.git &&
test_path_is_dir realgitdir/refs
'
ln -s here .git &&
git init --separate-git-dir ../realgitdir
) &&
- echo "gitdir: `pwd`/realgitdir" >expected &&
+ echo "gitdir: $(pwd)/realgitdir" >expected &&
test_cmp expected newdir/.git &&
test_cmp expected newdir/here &&
test_path_is_dir realgitdir/refs
test_cmp err.expect err
'
+test_expect_success NOT_MINGW,NOT_CYGWIN 'correct handling of backslashes' '
+ rm -rf whitespace &&
+ mkdir whitespace &&
+ >"whitespace/trailing 1 " &&
+ >"whitespace/trailing 2 \\\\" &&
+ >"whitespace/trailing 3 \\\\" &&
+ >"whitespace/trailing 4 \\ " &&
+ >"whitespace/trailing 5 \\ \\ " &&
+ >"whitespace/trailing 6 \\a\\" &&
+ >whitespace/untracked &&
+ echo "whitespace/trailing 1 \\ " >ignore &&
+ echo "whitespace/trailing 2 \\\\\\\\\\\\\\\\" >>ignore &&
+ echo "whitespace/trailing 3 \\\\\\\\\\\\\\\\ " >>ignore &&
+ echo "whitespace/trailing 4 \\\\\\\\\\\\ " >>ignore &&
+ echo "whitespace/trailing 5 \\\\\\\\ \\\\\\\\\\\\ " >>ignore &&
+ echo "whitespace/trailing 6 \\\\\\\\a\\\\\\\\" >>ignore &&
+ echo whitespace/untracked >expect &&
+ >err.expect &&
+ git ls-files -o -X ignore whitespace >actual 2>err &&
+ test_cmp expect actual &&
+ test_cmp err.expect err
+'
+
test_done
git update-index --add infocom
echo xyzzy >infocom
- files=`git diff-files -p`
+ files=$(git diff-files -p)
test_expect_success \
"Racy GIT trial #$trial part A" \
'test "" != "$files"'
echo xyzzy >cornerstone
git update-index --add cornerstone
- files=`git diff-files -p`
+ files=$(git diff-files -p)
test_expect_success \
"Racy GIT trial #$trial part B" \
'test "" != "$files"'
git commit -m initial &&
- one=`git rev-parse HEAD:one` &&
- dir=`git rev-parse HEAD:dir` &&
- two=`git rev-parse HEAD:dir/two` &&
- three=`git rev-parse HEAD:three` &&
+ one=$(git rev-parse HEAD:one) &&
+ dir=$(git rev-parse HEAD:dir) &&
+ two=$(git rev-parse HEAD:dir/two) &&
+ three=$(git rev-parse HEAD:three) &&
for w in Some extra lines here; do echo $w; done >>one &&
git diff >patch.file &&
- patched=`git hash-object --stdin <one` &&
+ patched=$(git hash-object --stdin <one) &&
git read-tree --reset -u HEAD &&
echo happy.
}
done &&
- differs=`git diff-index --cached HEAD` &&
+ differs=$(git diff-index --cached HEAD) &&
test -z "$differs" || {
echo Oops "$differs"
false
}
done &&
- differs=`git diff-index --cached HEAD` &&
+ differs=$(git diff-index --cached HEAD) &&
test -z "$differs" || {
echo Oops "$differs"
false
break
}
done &&
- test "$one" = `git hash-object --stdin <one` &&
- test "$two" = `git hash-object --stdin <dir/two` &&
- differs=`git diff-index --cached HEAD` &&
+ test "$one" = $(git hash-object --stdin <one) &&
+ test "$two" = $(git hash-object --stdin <dir/two) &&
+ differs=$(git diff-index --cached HEAD) &&
test -z "$differs" || {
echo Oops "$differs"
false
git update-index -- $f
fi
done &&
- test "$one" = `git hash-object --stdin <one` &&
- test "$two" = `git hash-object --stdin <dir/two` &&
- differs=`git diff-index --cached HEAD` &&
+ test "$one" = $(git hash-object --stdin <one) &&
+ test "$two" = $(git hash-object --stdin <dir/two) &&
+ differs=$(git diff-index --cached HEAD) &&
test -z "$differs" || {
echo Oops "$differs"
false
git read-tree --reset -u HEAD &&
git apply patch.file &&
- test "$patched" = "`git hash-object --stdin <one`" || {
+ test "$patched" = "$(git hash-object --stdin <one)" || {
echo "Eh? apply without index"
false
}
git read-tree --reset -u HEAD &&
git apply --cached patch.file &&
- test "$patched" = `git rev-parse :one` || {
+ test "$patched" = $(git rev-parse :one) || {
echo "Eh? apply with --cached"
false
}
git read-tree --reset -u HEAD &&
git apply --index patch.file &&
- test "$patched" = `git rev-parse :one` &&
- test "$patched" = `git hash-object --stdin <one` || {
+ test "$patched" = $(git rev-parse :one) &&
+ test "$patched" = $(git hash-object --stdin <one) || {
echo "Eh? apply with --index"
false
}
git read-tree --reset -u HEAD &&
git apply patch.file &&
- test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
+ test "$patched" = "$(remove_cr <one | git hash-object --stdin)" || {
echo "Eh? apply without index"
false
}
git read-tree --reset -u HEAD &&
git apply --cached patch.file &&
- test "$patched" = `git rev-parse :one` || {
+ test "$patched" = $(git rev-parse :one) || {
echo "Eh? apply without index"
false
}
git read-tree --reset -u HEAD &&
git apply --index patch.file &&
- test "$patched" = `git rev-parse :one` &&
- test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
+ test "$patched" = $(git rev-parse :one) &&
+ test "$patched" = "$(remove_cr <one | git hash-object --stdin)" || {
echo "Eh? apply with --index"
false
}
git commit -m initial &&
- one=`git rev-parse HEAD:one` &&
- two=`git rev-parse HEAD:two` &&
- three=`git rev-parse HEAD:three` &&
+ one=$(git rev-parse HEAD:one) &&
+ two=$(git rev-parse HEAD:two) &&
+ three=$(git rev-parse HEAD:three) &&
echo happy.
'
! has_cr one &&
has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
- threediff=`git diff three` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
+ threediff=$(git diff three) &&
test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
'
# Note, "normalized" means that git will normalize it if added
has_cr two &&
- twodiff=`git diff two` &&
+ twodiff=$(git diff two) &&
test -n "$twodiff"
'
# Note, "normalized" means that git will normalize it if added
has_cr two &&
- twodiff=`git diff two` &&
+ twodiff=$(git diff two) &&
test -n "$twodiff"
'
git read-tree --reset -u HEAD &&
has_cr one &&
- onediff=`git diff one` &&
+ onediff=$(git diff one) &&
test -z "$onediff"
'
git read-tree --reset -u HEAD &&
has_cr one &&
- onediff=`git diff one` &&
+ onediff=$(git diff one) &&
test -z "$onediff"
'
git read-tree --reset -u HEAD &&
! has_cr one &&
- onediff=`git diff one` &&
+ onediff=$(git diff one) &&
test -z "$onediff"
'
has_cr one &&
has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
- threediff=`git diff three` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
+ threediff=$(git diff three) &&
test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
'
has_cr one &&
has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
- threediff=`git diff three` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
+ threediff=$(git diff three) &&
test -z "$onediff" -a -n "$twodiff" -a -z "$threediff"
'
git read-tree --reset -u HEAD &&
! has_cr three &&
- threediff=`git diff three` &&
+ threediff=$(git diff three) &&
test -z "$threediff"
'
git read-tree --reset -u HEAD &&
has_cr three &&
- threediff=`git diff three` &&
+ threediff=$(git diff three) &&
test -z "$threediff"
'
git commit -m initial &&
- one=`git rev-parse HEAD:one` &&
- two=`git rev-parse HEAD:two` &&
+ one=$(git rev-parse HEAD:one) &&
+ two=$(git rev-parse HEAD:two) &&
echo happy.
'
! has_cr one &&
! has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
test -z "$onediff" -a -z "$twodiff"
'
has_cr one &&
! has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
test -z "$onediff" -a -z "$twodiff"
'
has_cr one &&
has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
test -z "$onediff" -a -z "$twodiff"
'
has_cr one &&
has_cr two &&
- onediff=`git diff one` &&
- twodiff=`git diff two` &&
+ onediff=$(git diff one) &&
+ twodiff=$(git diff two) &&
test -z "$onediff" -a -z "$twodiff"
'
test_expect_success \
'text without newline at end should end with newline' '
- test `printf "$ttt" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0
+ test $(printf "$ttt" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0
'
# text plus spaces at the end:
test_expect_success \
'text plus spaces without newline at end should end with newline' '
- test `printf "$ttt$sss" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
- test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0
+ test $(printf "$ttt$sss" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$sss$sss" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$ttt$sss$sss" | git stripspace | wc -l) -gt 0 &&
+ test $(printf "$ttt$sss$sss$sss" | git stripspace | wc -l) -gt 0
'
test_expect_success \
test_expect_success 'setup helper scripts' '
cat >dump <<-\EOF &&
- whoami=`echo $0 | sed s/.*git-credential-//`
+ whoami=$(echo $0 | sed s/.*git-credential-//)
echo >&2 "$whoami: $*"
OIFS=$IFS
IFS==
'rm -f .git/index F16 &&
echo F16 >F16 &&
git update-index --add F16 &&
- tree0=`git write-tree` &&
+ tree0=$(git write-tree) &&
echo E16 >F16 &&
git update-index F16 &&
- tree1=`git write-tree` &&
+ tree1=$(git write-tree) &&
read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 &&
git ls-files --stage'
}
check_cache_at () {
- clean_if_empty=`git diff-files -- "$1"`
+ clean_if_empty=$(git diff-files -- "$1")
case "$clean_if_empty" in
'') echo "$1: clean" ;;
?*) echo "$1: dirty" ;;
echo rezrov >rezrov &&
echo yomin >yomin &&
git update-index --add nitfol bozbar rezrov &&
- treeH=`git write-tree` &&
+ treeH=$(git write-tree) &&
echo treeH $treeH &&
git ls-tree $treeH &&
cat bozbar-new >bozbar &&
git update-index --add frotz bozbar --force-remove rezrov &&
git ls-files --stage >M.out &&
- treeM=`git write-tree` &&
+ treeM=$(git write-tree) &&
echo treeM $treeM &&
git ls-tree $treeM &&
git diff-tree $treeH $treeM'
'rm -f .git/index &&
echo DF >DF &&
git update-index --add DF &&
- treeDF=`git write-tree` &&
+ treeDF=$(git write-tree) &&
echo treeDF $treeDF &&
git ls-tree $treeDF &&
mkdir DF &&
echo DF/DF >DF/DF &&
git update-index --add --remove DF DF/DF &&
- treeDFDF=`git write-tree` &&
+ treeDFDF=$(git write-tree) &&
echo treeDFDF $treeDFDF &&
git ls-tree $treeDFDF &&
git ls-files --stage >DFDF.out'
'rm -f .git/index &&
: >a &&
git update-index --add a &&
- treeM=`git write-tree` &&
+ treeM=$(git write-tree) &&
echo treeM $treeM &&
git ls-tree $treeM &&
git ls-files --stage >treeM.out &&
git update-index --remove a &&
mkdir a &&
: >a/b &&
- treeH=`git write-tree` &&
+ treeH=$(git write-tree) &&
echo treeH $treeH &&
git ls-tree $treeH'
mkdir c &&
: >c/d &&
git update-index --add a c/d &&
- treeM=`git write-tree` &&
+ treeM=$(git write-tree) &&
echo treeM $treeM &&
git ls-tree $treeM &&
git ls-files --stage >treeM.out &&
mkdir a &&
: >a/b &&
git update-index --add --remove a a/b &&
- treeH=`git write-tree` &&
+ treeH=$(git write-tree) &&
echo treeH $treeH &&
git ls-tree $treeH'
}
check_cache_at () {
- clean_if_empty=`git diff-files -- "$1"`
+ clean_if_empty=$(git diff-files -- "$1")
case "$clean_if_empty" in
'') echo "$1: clean" ;;
?*) echo "$1: dirty" ;;
echo bozbar >bozbar &&
echo rezrov >rezrov &&
git update-index --add nitfol bozbar rezrov &&
- treeH=`git write-tree` &&
+ treeH=$(git write-tree) &&
echo treeH $treeH &&
git ls-tree $treeH &&
echo gnusto >bozbar &&
git update-index --add frotz bozbar --force-remove rezrov &&
git ls-files --stage >M.out &&
- treeM=`git write-tree` &&
+ treeM=$(git write-tree) &&
echo treeM $treeM &&
git ls-tree $treeM &&
sum bozbar frotz nitfol >M.sum &&
'rm -f .git/index &&
echo DF >DF &&
git update-index --add DF &&
- treeDF=`git write-tree` &&
+ treeDF=$(git write-tree) &&
echo treeDF $treeDF &&
git ls-tree $treeDF &&
mkdir DF &&
echo DF/DF >DF/DF &&
git update-index --add --remove DF DF/DF &&
- treeDFDF=`git write-tree` &&
+ treeDFDF=$(git write-tree) &&
echo treeDFDF $treeDFDF &&
git ls-tree $treeDFDF &&
git ls-files --stage >DFDF.out'
test_expect_success setup '
echo hello >one &&
git update-index --add one &&
- tree=`git write-tree` &&
+ tree=$(git write-tree) &&
echo tree is $tree
'
echo >file2 master creates untracked file2 &&
echo >subdir/file2 master creates untracked subdir/file2 &&
- if err=`read_tree_u_must_succeed -m -u master side 2>&1`
+ if err=$(read_tree_u_must_succeed -m -u master side 2>&1)
then
echo should have complained
false
test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
- if err=`read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1`
+ if err=$(read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1)
then
echo should have complained
false
test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
- if err=`read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+ if err=$(read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1)
then
echo should have complained
false
git checkout master &&
echo >file3 file three created in master, untracked &&
echo >subdir/file3 file three created in master, untracked &&
- if err=`read_tree_u_must_succeed -m -u branch-point master side 2>&1`
+ if err=$(read_tree_u_must_succeed -m -u branch-point master side 2>&1)
then
echo should have complained
false
test_expect_success 'update-index and ls-files' '
git update-index --add one &&
- case "`git ls-files`" in
+ case "$(git ls-files)" in
one) echo pass one ;;
*) echo bad one; exit 1 ;;
esac &&
(
cd dir &&
git update-index --add two &&
- case "`git ls-files`" in
+ case "$(git ls-files)" in
two) echo pass two ;;
*) echo bad two; exit 1 ;;
esac
) &&
- case "`git ls-files`" in
+ case "$(git ls-files)" in
dir/two"$LF"one) echo pass both ;;
*) echo bad; exit 1 ;;
esac
'
test_expect_success 'cat-file' '
- two=`git ls-files -s dir/two` &&
- two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` &&
+ two=$(git ls-files -s dir/two) &&
+ two=$(expr "$two" : "[0-7]* \\([0-9a-f]*\\)") &&
echo "$two" &&
git cat-file -p "$two" >actual &&
cmp dir/two actual &&
test_expect_success 'diff-files' '
echo a >>one &&
echo d >>dir/two &&
- case "`git diff-files --name-only`" in
+ case "$(git diff-files --name-only)" in
dir/two"$LF"one) echo pass top ;;
*) echo bad top; exit 1 ;;
esac &&
# diff should not omit leading paths
(
cd dir &&
- case "`git diff-files --name-only`" in
+ case "$(git diff-files --name-only)" in
dir/two"$LF"one) echo pass subdir ;;
*) echo bad subdir; exit 1 ;;
esac &&
- case "`git diff-files --name-only .`" in
+ case "$(git diff-files --name-only .)" in
dir/two) echo pass subdir limited ;;
*) echo bad subdir limited; exit 1 ;;
esac
'
test_expect_success 'write-tree' '
- top=`git write-tree` &&
+ top=$(git write-tree) &&
echo $top &&
(
cd dir &&
- sub=`git write-tree` &&
+ sub=$(git write-tree) &&
echo $sub &&
test "z$top" = "z$sub"
)
test_expect_success 'read-tree' '
rm -f one dir/two &&
- tree=`git write-tree` &&
+ tree=$(git write-tree) &&
read_tree_u_must_succeed --reset -u "$tree" &&
cmp one original.one &&
cmp dir/two original.two &&
'
test_expect_success 'index-pack' '
- git clone file://"`pwd`"/.git foo &&
+ git clone file://"$(pwd)"/.git foo &&
GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack
'
'
test_expect_success 'pack-objects with large loose object' '
- SHA1=`git hash-object huge` &&
+ SHA1=$(git hash-object huge) &&
test_create_repo loose &&
echo $SHA1 | git pack-objects --stdout |
GIT_ALLOC_LIMIT=0 GIT_DIR=loose/.git git unpack-objects &&
test_cmp expect .git/config
'
+test_expect_success POSIXPERM,PERL 'preserves existing permissions' '
+ chmod 0600 .git/config &&
+ git config imap.pass Hunter2 &&
+ perl -e \
+ "die q(badset) if ((stat(q(.git/config)))[2] & 07777) != 0600" &&
+ git config --rename-section imap pop &&
+ perl -e \
+ "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
+'
+
test_done
grep "fatal: unknown command: unknown $a" err
'
-test_expect_success 'stdin fails on badly quoted input' '
+test_expect_success 'stdin fails on unbalanced quotes' '
echo "create $a \"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"master" err
'
-test_expect_success 'stdin fails on arguments not separated by space' '
+test_expect_success 'stdin fails on invalid escape' '
+ echo "create $a \"ma\zter\"" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err
+'
+
+test_expect_success 'stdin fails on junk after quoted argument' '
echo "create \"$a\"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: expected SP but got: master" err
+ grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err
'
test_expect_success 'stdin fails create with no ref' '
echo "create " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create line missing <ref>" err
+ grep "fatal: create: missing <ref>" err
'
test_expect_success 'stdin fails create with bad ref name' '
test_expect_success 'stdin fails create with no new value' '
echo "create $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $a missing <newvalue>" err
+ grep "fatal: create $a: missing <newvalue>" err
'
test_expect_success 'stdin fails create with too many arguments' '
echo "create $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $a has extra input: $m" err
+ grep "fatal: create $a: extra input: $m" err
'
test_expect_success 'stdin fails update with no ref' '
echo "update " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: update line missing <ref>" err
+ grep "fatal: update: missing <ref>" err
'
test_expect_success 'stdin fails update with bad ref name' '
test_expect_success 'stdin fails update with no new value' '
echo "update $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: update $a missing <newvalue>" err
+ grep "fatal: update $a: missing <newvalue>" err
'
test_expect_success 'stdin fails update with too many arguments' '
echo "update $a $m $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: update $a has extra input: $m" err
+ grep "fatal: update $a: extra input: $m" err
'
test_expect_success 'stdin fails delete with no ref' '
echo "delete " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: delete line missing <ref>" err
+ grep "fatal: delete: missing <ref>" err
'
test_expect_success 'stdin fails delete with bad ref name' '
test_expect_success 'stdin fails delete with too many arguments' '
echo "delete $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: delete $a has extra input: $m" err
+ grep "fatal: delete $a: extra input: $m" err
'
test_expect_success 'stdin fails verify with too many arguments' '
echo "verify $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: verify $a has extra input: $m" err
+ grep "fatal: verify $a: extra input: $m" err
'
test_expect_success 'stdin fails option with unknown name' '
test_cmp expect actual
'
+test_expect_success 'stdin succeeds with quoted argument' '
+ git update-ref -d $a &&
+ echo "create $a \"$m\"" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin succeeds with escaped character' '
+ git update-ref -d $a &&
+ echo "create $a \"ma\\163ter\"" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'stdin update ref creates with zero old value' '
echo "update $b $m $Z" >stdin &&
git update-ref --stdin <stdin &&
test_expect_success 'stdin update ref fails with bad old value' '
echo "update $c $m does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+ grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with bad new value' '
echo "create $c does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+ grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with zero new value' '
echo "create $c " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $c given zero new value" err &&
+ grep "fatal: create $c: zero <newvalue>" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin delete ref fails with zero old value' '
echo "delete $a " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: delete $a given zero old value" err &&
+ grep "fatal: delete $a: zero <oldvalue>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
test_expect_success 'stdin -z fails create with no ref' '
printf $F "create " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create line missing <ref>" err
+ grep "fatal: create: missing <ref>" err
'
test_expect_success 'stdin -z fails create with bad ref name' '
test_expect_success 'stdin -z fails create with no new value' '
printf $F "create $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create $a missing <newvalue>" err
+ grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
'
test_expect_success 'stdin -z fails create with too many arguments' '
test_expect_success 'stdin -z fails update with no ref' '
printf $F "update " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update line missing <ref>" err
+ grep "fatal: update: missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails update with too few args' '
+ printf $F "update $a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails update with bad ref name' '
- printf $F "update ~a" "$m" >stdin &&
+ printf $F "update ~a" "$m" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: invalid ref format: ~a" err
'
+test_expect_success 'stdin -z emits warning with empty new value' '
+ git update-ref $a $m &&
+ printf $F "update $a" "" "" >stdin &&
+ git update-ref -z --stdin <stdin 2>err &&
+ grep "warning: update $a: missing <newvalue>, treating as zero" err &&
+ test_must_fail git rev-parse --verify -q $a
+'
+
test_expect_success 'stdin -z fails update with no new value' '
printf $F "update $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $a missing <newvalue>" err
+ grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
'
test_expect_success 'stdin -z fails update with no old value' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err
+ grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails update with too many arguments' '
test_expect_success 'stdin -z fails delete with no ref' '
printf $F "delete " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: delete line missing <ref>" err
+ grep "fatal: delete: missing <ref>" err
'
test_expect_success 'stdin -z fails delete with bad ref name' '
test_expect_success 'stdin -z fails delete with no old value' '
printf $F "delete $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err
+ grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails delete with too many arguments' '
test_expect_success 'stdin -z fails verify with no old value' '
printf $F "verify $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err
+ grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails option with unknown name' '
test_expect_success 'stdin -z update ref fails with bad old value' '
printf $F "update $c" "$m" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+ grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
git update-ref -d "$c" &&
printf $F "create $c" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+ grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
-test_expect_success 'stdin -z create ref fails with zero new value' '
+test_expect_success 'stdin -z create ref fails with empty new value' '
printf $F "create $c" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create $c given zero new value" err &&
+ grep "fatal: create $c: missing <newvalue>" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin -z delete ref fails with zero old value' '
printf $F "delete $a" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: delete $a given zero old value" err &&
+ grep "fatal: delete $a: zero <oldvalue>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
test_expect_success 'stdin -z update refs fails with wrong old value' '
git update-ref $c $m &&
- printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin &&
+ printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
'
+test_expect_success 'checkout should not delete log for packed ref' '
+ test $(git reflog master | wc -l) = 4 &&
+ git branch foo &&
+ git pack-refs --all &&
+ git checkout foo &&
+ test $(git reflog master | wc -l) = 4
+'
+
test_done
git branch -D new ;# can fail but is ok
git branch -t new my-side@{u} &&
git merge -s ours new@{u} &&
- git show -s --pretty=format:%s >actual &&
+ git show -s --pretty=tformat:%s >actual &&
echo "Merge remote-tracking branch ${sq}origin/side${sq}" >expect &&
test_cmp expect actual
)
test_i18ngrep "[Uu]sage: git update-index" broken/usage
'
+test_expect_success '--cacheinfo complains of missing arguments' '
+ test_must_fail git update-index --cacheinfo
+'
+
test_expect_success '--cacheinfo does not accept blob null sha1' '
echo content >file &&
git add file &&
. ./test-lib.sh
test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
-test_have_prereq GREP_STRIPS_CR && export GREP_OPTIONS=-U
+if test_have_prereq GREP_STRIPS_CR
+then
+ GREP_OPTIONS=-U
+ export GREP_OPTIONS
+fi
test_expect_success 'setup' '
conflict_hunks () {
SHELL=
export SHELL
+test_expect_success 'rebase --keep-empty' '
+ git checkout -b emptybranch master &&
+ git commit --allow-empty -m "empty" &&
+ git rebase --keep-empty -i HEAD~2 &&
+ git log --oneline >actual &&
+ test_line_count = 6 actual
+'
+
test_expect_success 'rebase -i with the exec command' '
git checkout master &&
(
testrebase " --merge" .git/rebase-merge
testrebase " --interactive" .git/rebase-merge
+test_expect_success 'abort rebase -i with --autostash' '
+ test_when_finished "git reset --hard" &&
+ echo uncommited-content >file0 &&
+ (
+ write_script abort-editor.sh <<-\EOF &&
+ echo >"$1"
+ EOF
+ test_set_editor "$(pwd)/abort-editor.sh" &&
+ test_must_fail git rebase -i --autostash HEAD^ &&
+ rm -f abort-editor.sh
+ ) &&
+ echo uncommited-content >expected &&
+ test_cmp expected file0
+'
+
test_done
cat <<-\EOF >expected &&
[master OBJID] second
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:14:13 2005 -0700
1 file changed, 1 insertion(+)
[master OBJID] third
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:15:13 2005 -0700
1 file changed, 1 insertion(+)
[master OBJID] fourth
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:16:13 2005 -0700
1 file changed, 1 insertion(+)
EOF
Trying simple merge.
[master OBJID] second
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:14:13 2005 -0700
1 file changed, 1 insertion(+)
Trying simple merge.
[master OBJID] third
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:15:13 2005 -0700
1 file changed, 1 insertion(+)
Trying simple merge.
[master OBJID] fourth
Author: A U Thor <author@example.com>
+ Date: Thu Apr 7 15:16:13 2005 -0700
1 file changed, 1 insertion(+)
EOF
git stash pop &&
git status --porcelain >actual &&
test_cmp expect actual &&
- test "1" = "`cat file2`" &&
- test untracked = "`cat untracked/untracked`"
+ test "1" = "$(cat file2)" &&
+ test untracked = "$(cat untracked/untracked)"
'
git clean --force --quiet -d
fi
# create utf-8 variables
-Adiarnfc=`printf '\303\204'`
-Adiarnfd=`printf 'A\314\210'`
+Adiarnfc=$(printf '\303\204')
+Adiarnfd=$(printf 'A\314\210')
-Odiarnfc=`printf '\303\226'`
-Odiarnfd=`printf 'O\314\210'`
-AEligatu=`printf '\303\206'`
-Invalidu=`printf '\303\377'`
+Odiarnfc=$(printf '\303\226')
+Odiarnfd=$(printf 'O\314\210')
+AEligatu=$(printf '\303\206')
+Invalidu=$(printf '\303\377')
#Create a string with 255 bytes (decomposed)
Alongc=$Alongc$AEligatu$AEligatu #254 Byte
test_expect_success "detect if nfd needed" '
- precomposeunicode=`git config core.precomposeunicode` &&
+ precomposeunicode=$(git config core.precomposeunicode) &&
test "$precomposeunicode" = true &&
git config core.precomposeunicode true
'
git add * &&
git commit -m "Long filename"
'
+
+test_expect_failure 'handle existing decomposed filenames' '
+ echo content >"verbatim.$Adiarnfd" &&
+ git -c core.precomposeunicode=false add "verbatim.$Adiarnfd" &&
+ git commit -m "existing decomposed file" &&
+ >expect &&
+ git ls-files --exclude-standard -o "verbatim*" >untracked &&
+ test_cmp expect untracked
+'
+
# Test if the global core.precomposeunicode stops autosensing
# Must be the last test case
test_expect_success "respect git config --global core.precomposeunicode" '
git config --global core.precomposeunicode true &&
rm -rf .git &&
git init &&
- precomposeunicode=`git config core.precomposeunicode` &&
+ precomposeunicode=$(git config core.precomposeunicode) &&
test "$precomposeunicode" = "true"
'
test_expect_success 'setup' '
echo frotz >rezrov &&
git update-index --add rezrov &&
- tree=`git write-tree` &&
+ tree=$(git write-tree) &&
echo $tree
'
mkdir path1 &&
echo rezrov >path1/file1 &&
git update-index --add file0 path1/file1 &&
- tree=`git write-tree` &&
+ tree=$(git write-tree) &&
echo "$tree" &&
echo nitfol >file0 &&
echo yomin >path1/file1 &&
mkdir path2 &&
echo rezrov >path2/file1 &&
git update-index --add path2/file1 &&
- tree3=`git write-tree` &&
+ tree3=$(git write-tree) &&
git diff --name-only $tree $tree3 -- "path2*1" "path1*1" >actual &&
cat <<-\EOF >expect &&
path1/file1
git diff >output &&
sed -e "s/-CIT/xCIT/" <output >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
- detected=`cat detected` &&
- detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
- detected=`sed -ne "${detected}p" broken` &&
+ detected=$(cat detected) &&
+ detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") &&
+ detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' '
git diff --binary | sed -e "s/-CIT/xCIT/" >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
- detected=`cat detected` &&
- detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
- detected=`sed -ne "${detected}p" broken` &&
+ detected=$(cat detected) &&
+ detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") &&
+ detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
test_expect_success 'diff-index with --binary' '
echo AIT >a && mv b e && echo CIT >c && cat e >d &&
git update-index --add --remove a b c d e &&
- tree0=`git write-tree` &&
+ tree0=$(git write-tree) &&
git diff --cached --binary >current &&
git apply --stat --summary current
'
test_expect_success 'apply binary patch' '
git reset --hard &&
git apply --binary --index <current &&
- tree1=`git write-tree` &&
+ tree1=$(git write-tree) &&
test "$tree1" = "$tree0"
'
+*++ [initial] Initial
EOF
-V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'`
+V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g')
while read cmd
do
case "$cmd" in
'' | '#'*) continue ;;
esac
- test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
- pfx=`printf "%04d" $test_count`
+ test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g')
+ pfx=$(printf "%04d" $test_count)
expect="$TEST_DIRECTORY/t4013/diff.$test"
actual="$pfx-diff.$test"
test_expect_success "format-patch --ignore-if-in-upstream" '
git format-patch --stdout master..side >patch0 &&
- cnt=`grep "^From " patch0 | wc -l` &&
+ cnt=$(grep "^From " patch0 | wc -l) &&
test $cnt = 3
'
git format-patch --stdout \
--ignore-if-in-upstream master..side >patch1 &&
- cnt=`grep "^From " patch1 | wc -l` &&
+ cnt=$(grep "^From " patch1 | wc -l) &&
test $cnt = 2
'
git checkout -b merger master &&
test_tick &&
git merge --no-ff slave &&
- cnt=`git format-patch -3 --stdout | grep "^From " | wc -l` &&
+ cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
test $cnt = 3
'
git checkout -b rebuild-0 master &&
git am -3 patch0 &&
- cnt=`git rev-list master.. | wc -l` &&
+ cnt=$(git rev-list master.. | wc -l) &&
test $cnt = 2
'
git checkout -b rebuild-1 master &&
git am -3 patch1 &&
- cnt=`git rev-list master.. | wc -l` &&
+ cnt=$(git rev-list master.. | wc -l) &&
test $cnt = 2
'
GIT_COMMITTER_NAME="はまの ふにおう" \
git format-patch -s --stdout -1 --attach >output &&
- test `grep -ci ^MIME-Version: output` = 1
+ test $(grep -ci ^MIME-Version: output) = 1
'
blob=$(echo file | git hash-object --stdin -w) &&
base_tree=$(echo "100644 blob $blob file" | git mktree) &&
trees= &&
- for i in `test_seq 1 40`
+ for i in $(test_seq 1 40)
do
blob=$(echo file$i | git hash-object --stdin -w) &&
trees="$trees$(echo "100644 blob $blob file" | git mktree)$LF"
test -z "$(git diff-files -- one)"
'
+test_expect_success POSIXPERM 'find-copies-harder is not confused by mode bits' '
+ echo content >exec &&
+ chmod +x exec &&
+ git add exec &&
+ git commit -m exec &&
+ git update-index --assume-unchanged exec &&
+ >expect &&
+ git diff-files --find-copies-harder -- exec >actual &&
+ test_cmp expect actual
+'
+
test_done
. ./test-lib.sh
# verify that diffc.expect matches output of
-# `git diff -c --name-only HEAD HEAD^ HEAD^2`
+# $(git diff -c --name-only HEAD HEAD^ HEAD^2)
diffc_verify () {
git diff -c --name-only HEAD HEAD^ HEAD^2 >diffc.actual &&
test_cmp diffc.expect diffc.actual
#include <stdio.h>
void print_int(int num);
-T/* a comment */
int func(int num);
int main() {
git config apply.ignorewhitespace change
test_expect_success 'patch2 applies (apply.ignorewhitespace = change)' '
- git apply patch2.patch
+ git apply patch2.patch &&
+ test_cmp main.c.final main.c
'
test_expect_success 'patch3 fails (missing string at EOL)' '
test_must_fail git apply patch4.patch
'
-test_expect_success 'patch5 applies (leading whitespace)' '
- git apply patch5.patch
-'
-
-test_expect_success 'patches do not mangle whitespace' '
- test_cmp main.c main.c.final
+test_expect_success 'patch5 fails (leading whitespace differences matter)' '
+ test_must_fail git apply patch5.patch
'
test_expect_success 're-create file (with --ignore-whitespace)' '
test_expect_success 'apply in forward' '
- T0=`git rev-parse "second^{tree}"` &&
+ T0=$(git rev-parse "second^{tree}") &&
git reset --hard initial &&
git apply --index --binary patch &&
- T1=`git write-tree` &&
+ T1=$(git write-tree) &&
test "$T0" = "$T1"
'
test_expect_success 'apply in forward without postimage' '
- T0=`git rev-parse "second^{tree}"` &&
+ T0=$(git rev-parse "second^{tree}") &&
(
cd initial &&
git apply --index --binary ../patch &&
- T1=`git write-tree` &&
+ T1=$(git write-tree) &&
test "$T0" = "$T1"
)
'
test_expect_success 'apply in reverse without postimage' '
- T0=`git rev-parse "initial^{tree}"` &&
+ T0=$(git rev-parse "initial^{tree}") &&
(
cd second &&
git apply --index --binary --reverse ../patch &&
- T1=`git write-tree` &&
+ T1=$(git write-tree) &&
test "$T0" = "$T1"
)
'
check_result sub/file1
'
-D=`pwd`
+D=$(pwd)
test_expect_success 'apply --whitespace=strip in subdir' '
test_cmp expect actual
'
+test_expect_success 'shortlog with revision pseudo options' '
+ git shortlog --all &&
+ git shortlog --branches &&
+ git shortlog --exclude=refs/heads/m* --all
+'
+
test_done
. ./test-lib.sh
test_expect_success 'setup' '
- test_commit initial foo a &&
- test_commit first foo b &&
- git checkout -b same HEAD^ &&
- test_commit same-msg foo b &&
- git checkout -b notsame HEAD^ &&
- test_commit notsame-msg foo c
+ as="a a a a a a a a" && # eight a
+ test_write_lines $as >foo &&
+ test_write_lines $as >bar &&
+ git add foo bar &&
+ git commit -a -m initial &&
+ test_write_lines $as b >foo &&
+ test_write_lines $as b >bar &&
+ git commit -a -m first &&
+ git checkout -b same master &&
+ git commit --amend -m same-msg &&
+ git checkout -b notsame master &&
+ echo c >foo &&
+ echo c >bar &&
+ git commit --amend -a -m notsame-msg &&
+ test_write_lines bar foo >bar-then-foo &&
+ test_write_lines foo bar >foo-then-bar
'
test_expect_success 'patch-id output is well-formed' '
- git log -p -1 | git patch-id > output &&
+ git log -p -1 | git patch-id >output &&
grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
'
+#calculate patch id. Make sure output is not empty.
calc_patch_id () {
- git patch-id |
- sed "s# .*##" > patch-id_"$1"
+ name="$1"
+ shift
+ git patch-id "$@" |
+ sed "s/ .*//" >patch-id_"$name" &&
+ test_line_count -gt 0 patch-id_"$name"
+}
+
+get_top_diff () {
+ git log -p -1 "$@" -O bar-then-foo --
}
get_patch_id () {
- git log -p -1 "$1" | git patch-id |
- sed "s# .*##" > patch-id_"$1"
+ get_top_diff "$1" | calc_patch_id "$@"
}
test_expect_success 'patch-id detects equality' '
git checkout same &&
git format-patch -1 --stdout | calc_patch_id same &&
test_cmp patch-id_master patch-id_same &&
- set `git format-patch -1 --stdout | git patch-id` &&
- test "$2" = `git rev-parse HEAD`
+ set $(git format-patch -1 --stdout | git patch-id) &&
+ test "$2" = $(git rev-parse HEAD)
'
test_expect_success 'whitespace is irrelevant in footer' '
test_cmp patch-id_master patch-id_same
'
+cmp_patch_id () {
+ if
+ test "$1" = "relevant"
+ then
+ ! test_cmp patch-id_"$2" patch-id_"$3"
+ else
+ test_cmp patch-id_"$2" patch-id_"$3"
+ fi
+}
+
+test_patch_id_file_order () {
+ relevant="$1"
+ shift
+ name="order-${1}-$relevant"
+ shift
+ get_top_diff "master" | calc_patch_id "$name" "$@" &&
+ git checkout same &&
+ git format-patch -1 --stdout -O foo-then-bar |
+ calc_patch_id "ordered-$name" "$@" &&
+ cmp_patch_id $relevant "$name" "ordered-$name"
+
+}
+
+# combined test for options: add more tests here to make them
+# run with all options
+test_patch_id () {
+ test_patch_id_file_order "$@"
+}
+
+# small tests with detailed diagnostic for basic options.
+test_expect_success 'file order is irrelevant with --stable' '
+ test_patch_id_file_order irrelevant --stable --stable
+'
+
+test_expect_success 'file order is relevant with --unstable' '
+ test_patch_id_file_order relevant --unstable --unstable
+'
+
+#Now test various option combinations.
+test_expect_success 'default is unstable' '
+ test_patch_id relevant default
+'
+
+test_expect_success 'patchid.stable = true is stable' '
+ test_config patchid.stable true &&
+ test_patch_id irrelevant patchid.stable=true
+'
+
+test_expect_success 'patchid.stable = false is unstable' '
+ test_config patchid.stable false &&
+ test_patch_id relevant patchid.stable=false
+'
+
+test_expect_success '--unstable overrides patchid.stable = true' '
+ test_config patchid.stable true &&
+ test_patch_id relevant patchid.stable=true--unstable --unstable
+'
+
+test_expect_success '--stable overrides patchid.stable = false' '
+ test_config patchid.stable false &&
+ test_patch_id irrelevant patchid.stable=false--stable --stable
+'
+
test_expect_success 'patch-id supports git-format-patch MIME output' '
get_patch_id master &&
git checkout same &&
'add files to repository' \
'find a -type f | xargs git update-index --add &&
find a -type l | xargs git update-index --add &&
- treeid=`git write-tree` &&
+ treeid=$(git write-tree) &&
echo $treeid >treeid &&
git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
git commit-tree $treeid </dev/null)'
test_expect_success 'clients cannot access unreachable commits' '
test_commit unreachable &&
- sha1=`git rev-parse HEAD` &&
+ sha1=$(git rev-parse HEAD) &&
git reset --hard HEAD^ &&
git archive $sha1 >remote.tar &&
test_must_fail git archive --remote=. $sha1 >remote.tar
test_expect_success 'upload-archive can allow unreachable commits' '
test_commit unreachable1 &&
- sha1=`git rev-parse HEAD` &&
+ sha1=$(git rev-parse HEAD) &&
git reset --hard HEAD^ &&
git archive $sha1 >remote.tar &&
test_config uploadarchive.allowUnreachable true &&
git request-pull initial "$downstream_url" tags/full:refs/tags/full
) >request &&
sed -nf fuzz.sed <request >request.fuzzy &&
- test_i18ncmp expect request.fuzzy
+ test_i18ncmp expect request.fuzzy &&
+
+ (
+ cd local &&
+ git request-pull initial "$downstream_url" full
+ ) >request &&
+ grep " tags/full\$" request
'
test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' '
)
'
-if test -n "$NO_CURL" -o -z "$GIT_TEST_HTTPD"; then
- say 'skipping remaining tests, git built without http support'
- test_done
-fi
-
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5537'}
-. "$TEST_DIRECTORY"/lib-httpd.sh
-start_httpd
-
-test_expect_success 'clone http repository' '
- git clone --bare --no-local shallow "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git clone $HTTPD_URL/smart/repo.git clone &&
- (
- cd clone &&
- git fsck &&
- git log --format=%s origin/master >actual &&
- cat <<EOF >expect &&
-7
-6
-5
-4
-3
-EOF
- test_cmp expect actual
- )
-'
-
test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' '
cp -R .git read-only.git &&
find read-only.git -print | xargs chmod -w &&
test_cmp expect actual
'
-stop_httpd
test_done
git cat-file blob `echo 1|git hash-object --stdin` >/dev/null
)
'
-
-if test -n "$NO_CURL" -o -z "$GIT_TEST_HTTPD"; then
- say 'skipping remaining tests, git built without http support'
- test_done
-fi
-
-. "$TEST_DIRECTORY"/lib-httpd.sh
-start_httpd
-
-test_expect_success 'push to shallow repo via http' '
- git clone --bare --no-local shallow "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- (
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git config http.receivepack true
- ) &&
- (
- cd full &&
- commit 9 &&
- git push $HTTPD_URL/smart/repo.git +master:refs/remotes/top/master
- ) &&
- (
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git fsck &&
- git log --format=%s top/master >actual &&
- cat <<EOF >expect &&
-9
-4
-3
-EOF
- test_cmp expect actual
- )
-'
-
-test_expect_success 'push from shallow repo via http' '
- mv "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" shallow-upstream.git &&
- git clone --bare --no-local full "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- (
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git config http.receivepack true
- ) &&
- commit 10 &&
- git push $HTTPD_URL/smart/repo.git +master:refs/remotes/top/master &&
- (
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git fsck &&
- git log --format=%s top/master >actual &&
- cat <<EOF >expect &&
-10
-1
-4
-3
-2
-1
-EOF
- test_cmp expect actual
- )
-'
-
-stop_httpd
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='push from/to a shallow clone over http'
+
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ say 'skipping test, git built without http support'
+ test_done
+fi
+
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+commit() {
+ echo "$1" >tracked &&
+ git add tracked &&
+ git commit -m "$1"
+}
+
+test_expect_success 'setup' '
+ git config --global transfer.fsckObjects true &&
+ commit 1 &&
+ commit 2 &&
+ commit 3 &&
+ commit 4 &&
+ git clone . full &&
+ (
+ git init full-abc &&
+ cd full-abc &&
+ commit a &&
+ commit b &&
+ commit c
+ ) &&
+ git clone --no-local --depth=2 .git shallow &&
+ git --git-dir=shallow/.git log --format=%s >actual &&
+ cat <<EOF >expect &&
+4
+3
+EOF
+ test_cmp expect actual &&
+ git clone --no-local --depth=2 full-abc/.git shallow2 &&
+ git --git-dir=shallow2/.git log --format=%s >actual &&
+ cat <<EOF >expect &&
+c
+b
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'push to shallow repo via http' '
+ git clone --bare --no-local shallow "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git config http.receivepack true
+ ) &&
+ (
+ cd full &&
+ commit 9 &&
+ git push $HTTPD_URL/smart/repo.git +master:refs/remotes/top/master
+ ) &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git fsck &&
+ git log --format=%s top/master >actual &&
+ cat <<EOF >expect &&
+9
+4
+3
+EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'push from shallow repo via http' '
+ mv "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" shallow-upstream.git &&
+ git clone --bare --no-local full "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git config http.receivepack true
+ ) &&
+ commit 10 &&
+ git push $HTTPD_URL/smart/repo.git +master:refs/remotes/top/master &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git fsck &&
+ git log --format=%s top/master >actual &&
+ cat <<EOF >expect &&
+10
+4
+3
+2
+1
+EOF
+ test_cmp expect actual
+ )
+'
+
+stop_httpd
+test_done
HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
-test_have_prereq GREP_STRIPS_CR && export GREP_OPTIONS=-U
+if test_have_prereq GREP_STRIPS_CR
+then
+ GREP_OPTIONS=-U
+ export GREP_OPTIONS
+fi
run_backend() {
echo "$2" |
--- /dev/null
+#!/bin/sh
+
+test_description='git-merge with case-changing rename on case-insensitive file system'
+
+. ./test-lib.sh
+
+if ! test_have_prereq CASE_INSENSITIVE_FS
+then
+ skip_all='skipping case insensitive tests - case sensitive file system'
+ test_done
+fi
+
+test_expect_success 'merge with case-changing rename' '
+ test $(git config core.ignorecase) = true &&
+ >TestCase &&
+ git add TestCase &&
+ git commit -m "add TestCase" &&
+ git tag baseline
+ git checkout -b with-camel &&
+ >foo &&
+ git add foo &&
+ git commit -m "intervening commit" &&
+ git checkout master &&
+ git rm TestCase &&
+ >testcase &&
+ git add testcase &&
+ git commit -m "rename to testcase" &&
+ git checkout with-camel &&
+ git merge master -m "merge" &&
+ test_path_is_file testcase
+'
+
+test_expect_success 'merge with case-changing rename on both sides' '
+ git checkout master &&
+ git reset --hard baseline &&
+ git branch -D with-camel &&
+ git checkout -b with-camel &&
+ git mv TestCase testcase &&
+ git commit -m "recase on branch" &&
+ >foo &&
+ git add foo &&
+ git commit -m "intervening commit" &&
+ git checkout master &&
+ git rm TestCase &&
+ >testcase &&
+ git add testcase &&
+ git commit -m "rename to testcase" &&
+ git checkout with-camel &&
+ git merge master -m "merge" &&
+ test_path_is_file testcase
+'
+
+test_done
test_cmp expected actual
'
+test_expect_success 'setup a fake editor' '
+ write_script fakeeditor <<-\EOF
+ sed -e "s/A U Thor/A fake Thor/" "$1" >"$1.new"
+ mv "$1.new" "$1"
+ EOF
+'
+
+test_expect_success '--edit with and without already replaced object' '
+ test_must_fail env GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" &&
+ GIT_EDITOR=./fakeeditor git replace --force --edit "$PARA3" &&
+ git replace -l | grep "$PARA3" &&
+ git cat-file commit "$PARA3" | grep "A fake Thor" &&
+ git replace -d "$PARA3" &&
+ GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" &&
+ git replace -l | grep "$PARA3" &&
+ git cat-file commit "$PARA3" | grep "A fake Thor"
+'
+
+test_expect_success '--edit and change nothing or command failed' '
+ git replace -d "$PARA3" &&
+ test_must_fail env GIT_EDITOR=true git replace --edit "$PARA3" &&
+ test_must_fail env GIT_EDITOR="./fakeeditor;false" git replace --edit "$PARA3" &&
+ GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" &&
+ git replace -l | grep "$PARA3" &&
+ git cat-file commit "$PARA3" | grep "A fake Thor"
+'
+
test_expect_success 'replace ref cleanup' '
test -n "$(git replace)" &&
git replace -d $(git replace) &&
test_cmp expect actual
'
+run_with_limited_stack () {
+ (ulimit -s 64 && "$@")
+}
+
+test_lazy_prereq ULIMIT 'run_with_limited_stack true'
+
+# we require ulimit, this excludes Windows
+test_expect_success ULIMIT '--contains works in a deep repo' '
+ >expect &&
+ i=1 &&
+ while test $i -lt 4000
+ do
+ echo "commit refs/heads/master
+committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200
+data <<EOF
+commit #$i
+EOF"
+ test $i = 1 && echo "from refs/heads/master^0"
+ i=$(($i + 1))
+ done | git fast-import &&
+ git checkout master &&
+ git tag far-far-away HEAD^ &&
+ run_with_limited_stack git tag --contains HEAD >actual &&
+ test_cmp expect actual
+'
+
test_done
git tag -m "annotated tag" annotated &&
git checkout -b side HEAD^^ &&
test_commit side2 &&
- test_commit side3
+ test_commit side3 &&
+ test_merge merge main3
'
test_expect_success 'showing two commits' '
'
test_expect_success '-s suppresses diff' '
- echo main3 >expect &&
- git show -s --format=%s main3 >actual &&
+ cat >expect <<-\EOF &&
+ merge
+ main3
+ EOF
+ git show -s --format=%s merge main3 >actual &&
test_cmp expect actual
'
git add foo &&
>empty &&
git commit --allow-empty-message <empty &&
- commit_msg_is ""
+ commit_msg_is "" &&
+ git tag empty-message-commit
'
test_expect_success 'Commit without message is no-no without --allow-empty-message' '
commit_msg_is "hello there"
'
+test_expect_success 'commit -C empty respects --allow-empty-message' '
+ echo more >>foo &&
+ git add foo &&
+ test_must_fail git commit -C empty-message-commit &&
+ git commit -C empty-message-commit --allow-empty-message &&
+ commit_msg_is ""
+'
+
commit_for_rebase_autosquash_setup () {
echo "first content line" >>foo &&
git add foo &&
'
-test_expect_success 'commit complains about bogus date' '
- test_must_fail git commit --amend --date=10.11.2010
+test_expect_success 'commit mentions forced date in output' '
+ git commit --amend --date=2010-01-02T03:04:05 >output &&
+ grep "Date: *Sat Jan 2 03:04:05 2010" output
+'
+
+test_expect_success 'commit complains about completely bogus dates' '
+ test_must_fail git commit --amend --date=seventeen
+'
+
+test_expect_success 'commit --date allows approxidate' '
+ git commit --amend \
+ --date="midnight the 12th of october, anno domini 1979" &&
+ echo "Fri Oct 12 00:00:00 1979 +0000" >expect &&
+ git log -1 --format=%ad >actual &&
+ test_cmp expect actual
'
test_expect_success 'sign off (1)' '
.git/COMMIT_EDITMSG
'
+test_expect_success 'message shows date when it is explicitly set' '
+ git commit --allow-empty -e -m foo --date="2010-01-02T03:04:05" &&
+ test_i18ngrep \
+ "^# Date: *Sat Jan 2 03:04:05 2010 +0000" \
+ .git/COMMIT_EDITMSG
+'
+
test_expect_success AUTOIDENT 'message shows committer when it is automatic' '
echo >>negative &&
test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
'
+test_expect_success 'switch core.commentchar' '
+ test_commit "#foo" foo &&
+ GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
+ test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
+'
+
+test_expect_success 'switch core.commentchar but out of options' '
+ cat >text <<\EOF &&
+# 1
+; 2
+@ 3
+! 4
+$ 5
+% 6
+^ 7
+& 8
+| 9
+: 10
+EOF
+ git commit --amend -F text &&
+ (
+ test_set_editor .git/FAKE_EDITOR &&
+ test_must_fail git -c core.commentChar=auto commit --amend
+ )
+'
+
test_done
test_expect_success "status (core.commentchar with two chars with submodule summary)" '
test_config core.commentchar ";;" &&
- git -c status.displayCommentPrefix=true status >output &&
- test_i18ncmp expect output
+ test_must_fail git -c status.displayCommentPrefix=true status
'
test_expect_success "--ignore-submodules=all suppresses submodule summary" '
git log --no-merges ^HEAD c2 c3
} >squash.1-5-9 &&
: >msg.nologff &&
- echo >msg.nolognoff &&
+ : >msg.nolognoff &&
{
echo "* tag 'c3':" &&
- echo " commit 3" &&
- echo
+ echo " commit 3"
} >msg.log
}
git diff --exit-code &&
if test -n "$3"
then
- git show -s --pretty=format:%s HEAD >msg.act &&
+ git show -s --pretty=tformat:%s HEAD >msg.act &&
test_cmp "$3" msg.act
fi
}
git tag c6 &&
git branch -f c5-branch c5 &&
git merge c5-branch~1 &&
- git show -s --pretty=format:%s HEAD >actual.branch &&
+ git show -s --pretty=tformat:%s HEAD >actual.branch &&
git reset --keep HEAD^ &&
git merge c5~1 &&
- git show -s --pretty=format:%s HEAD >actual.tag &&
+ git show -s --pretty=tformat:%s HEAD >actual.tag &&
test_cmp expected.branch actual.branch &&
test_cmp expected.tag actual.tag
'
test_expect_success PERL 'difftool ignores bad --tool values' '
: >expect &&
- test_expect_code 1 \
+ test_must_fail \
git difftool --no-prompt --tool=bad-tool branch >actual &&
test_cmp expect actual
'
test $(grep -c " " actual) = 9
'
+test_expect_success 'blaming files with CRLF newlines' '
+ git config core.autocrlf false &&
+ printf "testcase\r\n" >crlffile &&
+ git add crlffile &&
+ git commit -m testcase &&
+ git -c core.autocrlf=input blame crlffile >actual &&
+ grep "A U Thor" actual
+'
+
test_done
git commit -m "prep only add" &&
git p4 submit --prepare-p4-only >out &&
test_i18ngrep "prepared for submission" out &&
- test_i18ngrep "must be deleted" out
+ test_i18ngrep "must be deleted" out &&
+ ! test_i18ngrep "everything below this line is just the diff" out
) &&
(
cd "$cli" &&
'
test_expect_success 'prompt - zsh color pc mode' '
- printf "BEFORE: (%%F{green}\${__git_ps1_branch_name}%%f):AFTER\\nmaster" >expected &&
+ printf "BEFORE: (%%F{green}master%%f):AFTER" >expected &&
(
ZSH_VERSION=5.0.0 &&
GIT_PS1_SHOWCOLORHINTS=y &&
- __git_ps1 "BEFORE:" ":AFTER" >"$actual"
- printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
) &&
test_cmp expected "$actual"
'
fi
}
+# This function writes out its parameters, one per line
+test_write_lines () {
+ printf "%s\n" "$@"
+}
+
perl () {
command "$PERL_PATH" "$@"
}
static struct child_process *get_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
- struct argv_array argv = ARGV_ARRAY_INIT;
struct strbuf buf = STRBUF_INIT;
struct child_process *helper;
const char **refspecs = NULL;
helper->in = -1;
helper->out = -1;
helper->err = 0;
- argv_array_pushf(&argv, "git-remote-%s", data->name);
- argv_array_push(&argv, transport->remote->name);
- argv_array_push(&argv, remove_ext_force(transport->url));
- helper->argv = argv_array_detach(&argv, NULL);
+ argv_array_pushf(&helper->args, "git-remote-%s", data->name);
+ argv_array_push(&helper->args, transport->remote->name);
+ argv_array_push(&helper->args, remove_ext_force(transport->url));
helper->git_cmd = 0;
helper->silent_exec_failure = 1;
close(data->helper->out);
fclose(data->out);
res = finish_command(data->helper);
- argv_array_free_detached(data->helper->argv);
free(data->helper);
data->helper = NULL;
}
{
struct child_process *helper = get_helper(transport);
struct helper_data *data = transport->data;
- struct argv_array argv = ARGV_ARRAY_INIT;
int cat_blob_fd, code;
memset(fastimport, 0, sizeof(*fastimport));
fastimport->in = helper->out;
- argv_array_push(&argv, "fast-import");
- argv_array_push(&argv, debug ? "--stats" : "--quiet");
+ argv_array_push(&fastimport->args, "fast-import");
+ argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
if (data->bidi_import) {
cat_blob_fd = xdup(helper->in);
- argv_array_pushf(&argv, "--cat-blob-fd=%d", cat_blob_fd);
+ argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd);
}
- fastimport->argv = argv.argv;
fastimport->git_cmd = 1;
code = start_command(fastimport);
{
struct helper_data *data = transport->data;
struct child_process *helper = get_helper(transport);
- int argc = 0, i;
- struct strbuf tmp = STRBUF_INIT;
+ int i;
memset(fastexport, 0, sizeof(*fastexport));
/* we need to duplicate helper->in because we want to use it after
* fastexport is done with it. */
fastexport->out = dup(helper->in);
- fastexport->argv = xcalloc(6 + revlist_args->nr, sizeof(*fastexport->argv));
- fastexport->argv[argc++] = "fast-export";
- fastexport->argv[argc++] = "--use-done-feature";
- fastexport->argv[argc++] = data->signed_tags ?
- "--signed-tags=verbatim" : "--signed-tags=warn-strip";
- if (data->export_marks) {
- strbuf_addf(&tmp, "--export-marks=%s.tmp", data->export_marks);
- fastexport->argv[argc++] = strbuf_detach(&tmp, NULL);
- }
- if (data->import_marks) {
- strbuf_addf(&tmp, "--import-marks=%s", data->import_marks);
- fastexport->argv[argc++] = strbuf_detach(&tmp, NULL);
- }
+ argv_array_push(&fastexport->args, "fast-export");
+ argv_array_push(&fastexport->args, "--use-done-feature");
+ argv_array_push(&fastexport->args, data->signed_tags ?
+ "--signed-tags=verbatim" : "--signed-tags=warn-strip");
+ if (data->export_marks)
+ argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks);
+ if (data->import_marks)
+ argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks);
for (i = 0; i < revlist_args->nr; i++)
- fastexport->argv[argc++] = revlist_args->items[i].string;
+ argv_array_push(&fastexport->args, revlist_args->items[i].string);
fastexport->git_cmd = 1;
return start_command(fastexport);
if (finish_command(&fastimport))
die("Error while running fast-import");
- argv_array_free_detached(fastimport.argv);
/*
* The fast-import stream of a remote helper that advertises
#include "diffcore.h"
#include "tree.h"
-static void show_entry(struct diff_options *opt, const char *prefix,
- struct tree_desc *desc, struct strbuf *base);
+/*
+ * internal mode marker, saying a tree entry != entry of tp[imin]
+ * (see ll_diff_tree_paths for what it means there)
+ *
+ * we will update/use/emit entry for diff only with it unset.
+ */
+#define S_IFXMIN_NEQ S_DIFFTREE_IFXMIN_NEQ
-static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
- struct strbuf *base, struct diff_options *opt)
-{
- unsigned mode1, mode2;
- const char *path1, *path2;
- const unsigned char *sha1, *sha2;
- int cmp, pathlen1, pathlen2;
- int old_baselen = base->len;
- sha1 = tree_entry_extract(t1, &path1, &mode1);
- sha2 = tree_entry_extract(t2, &path2, &mode2);
+static struct combine_diff_path *ll_diff_tree_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parents_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt);
+static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+ struct strbuf *base, struct diff_options *opt);
+
+/*
+ * Compare two tree entries, taking into account only path/S_ISDIR(mode),
+ * but not their sha1's.
+ *
+ * NOTE files and directories *always* compare differently, even when having
+ * the same name - thanks to base_name_compare().
+ *
+ * NOTE empty (=invalid) descriptor(s) take part in comparison as +infty,
+ * so that they sort *after* valid tree entries.
+ *
+ * Due to this convention, if trees are scanned in sorted order, all
+ * non-empty descriptors will be processed first.
+ */
+static int tree_entry_pathcmp(struct tree_desc *t1, struct tree_desc *t2)
+{
+ struct name_entry *e1, *e2;
+ int cmp;
- pathlen1 = tree_entry_len(&t1->entry);
- pathlen2 = tree_entry_len(&t2->entry);
- cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
- if (cmp < 0) {
- show_entry(opt, "-", t1, base);
+ /* empty descriptors sort after valid tree entries */
+ if (!t1->size)
+ return t2->size ? 1 : 0;
+ else if (!t2->size)
return -1;
- }
- if (cmp > 0) {
- show_entry(opt, "+", t2, base);
- return 1;
- }
- if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
- return 0;
- /*
- * If the filemode has changed to/from a directory from/to a regular
- * file, we need to consider it a remove and an add.
- */
- if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
- show_entry(opt, "-", t1, base);
- show_entry(opt, "+", t2, base);
- return 0;
- }
+ e1 = &t1->entry;
+ e2 = &t2->entry;
+ cmp = base_name_compare(e1->path, tree_entry_len(e1), e1->mode,
+ e2->path, tree_entry_len(e2), e2->mode);
+ return cmp;
+}
- strbuf_add(base, path1, pathlen1);
- if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
- opt->change(opt, mode1, mode2,
- sha1, sha2, 1, 1, base->buf, 0, 0);
+
+/*
+ * convert path -> opt->diff_*() callbacks
+ *
+ * emits diff to first parent only, and tells diff tree-walker that we are done
+ * with p and it can be freed.
+ */
+static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_diff_path *p)
+{
+ struct combine_diff_parent *p0 = &p->parent[0];
+ if (p->mode && p0->mode) {
+ opt->change(opt, p0->mode, p->mode, p0->sha1, p->sha1,
+ 1, 1, p->path, 0, 0);
+ }
+ else {
+ const unsigned char *sha1;
+ unsigned int mode;
+ int addremove;
+
+ if (p->mode) {
+ addremove = '+';
+ sha1 = p->sha1;
+ mode = p->mode;
+ } else {
+ addremove = '-';
+ sha1 = p0->sha1;
+ mode = p0->mode;
}
- strbuf_addch(base, '/');
- diff_tree_sha1(sha1, sha2, base->buf, opt);
- } else {
- opt->change(opt, mode1, mode2, sha1, sha2, 1, 1, base->buf, 0, 0);
+
+ opt->add_remove(opt, addremove, mode, sha1, 1, p->path, 0);
}
- strbuf_setlen(base, old_baselen);
- return 0;
+
+ return 0; /* we are done with p */
}
-/* A whole sub-tree went away or appeared */
-static void show_tree(struct diff_options *opt, const char *prefix,
- struct tree_desc *desc, struct strbuf *base)
+
+/*
+ * Make a new combine_diff_path from path/mode/sha1
+ * and append it to paths list tail.
+ *
+ * Memory for created elements could be reused:
+ *
+ * - if last->next == NULL, the memory is allocated;
+ *
+ * - if last->next != NULL, it is assumed that p=last->next was returned
+ * earlier by this function, and p->next was *not* modified.
+ * The memory is then reused from p.
+ *
+ * so for clients,
+ *
+ * - if you do need to keep the element
+ *
+ * p = path_appendnew(p, ...);
+ * process(p);
+ * p->next = NULL;
+ *
+ * - if you don't need to keep the element after processing
+ *
+ * pprev = p;
+ * p = path_appendnew(p, ...);
+ * process(p);
+ * p = pprev;
+ * ; don't forget to free tail->next in the end
+ *
+ * p->parent[] remains uninitialized.
+ */
+static struct combine_diff_path *path_appendnew(struct combine_diff_path *last,
+ int nparent, const struct strbuf *base, const char *path, int pathlen,
+ unsigned mode, const unsigned char *sha1)
{
- enum interesting match = entry_not_interesting;
- for (; desc->size; update_tree_entry(desc)) {
- if (match != all_entries_interesting) {
- match = tree_entry_interesting(&desc->entry, base, 0,
- &opt->pathspec);
- if (match == all_entries_not_interesting)
- break;
- if (match == entry_not_interesting)
- continue;
- }
- show_entry(opt, prefix, desc, base);
+ struct combine_diff_path *p;
+ int len = base->len + pathlen;
+ int alloclen = combine_diff_path_size(nparent, len);
+
+ /* if last->next is !NULL - it is a pre-allocated memory, we can reuse */
+ p = last->next;
+ if (p && (alloclen > (intptr_t)p->next)) {
+ free(p);
+ p = NULL;
+ }
+
+ if (!p) {
+ p = xmalloc(alloclen);
+
+ /*
+ * until we go to it next round, .next holds how many bytes we
+ * allocated (for faster realloc - we don't need copying old data).
+ */
+ p->next = (struct combine_diff_path *)(intptr_t)alloclen;
}
+
+ last->next = p;
+
+ p->path = (char *)&(p->parent[nparent]);
+ memcpy(p->path, base->buf, base->len);
+ memcpy(p->path + base->len, path, pathlen);
+ p->path[len] = 0;
+ p->mode = mode;
+ hashcpy(p->sha1, sha1 ? sha1 : null_sha1);
+
+ return p;
}
-/* A file entry went away or appeared */
-static void show_entry(struct diff_options *opt, const char *prefix,
- struct tree_desc *desc, struct strbuf *base)
+/*
+ * new path should be added to combine diff
+ *
+ * 3 cases on how/when it should be called and behaves:
+ *
+ * t, !tp -> path added, all parents lack it
+ * !t, tp -> path removed from all parents
+ * t, tp -> path modified/added
+ * (M for tp[i]=tp[imin], A otherwise)
+ */
+static struct combine_diff_path *emit_path(struct combine_diff_path *p,
+ struct strbuf *base, struct diff_options *opt, int nparent,
+ struct tree_desc *t, struct tree_desc *tp,
+ int imin)
{
unsigned mode;
const char *path;
- const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
- int pathlen = tree_entry_len(&desc->entry);
+ const unsigned char *sha1;
+ int pathlen;
int old_baselen = base->len;
+ int i, isdir, recurse = 0, emitthis = 1;
+
+ /* at least something has to be valid */
+ assert(t || tp);
- strbuf_add(base, path, pathlen);
- if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
- enum object_type type;
- struct tree_desc inner;
- void *tree;
- unsigned long size;
+ if (t) {
+ /* path present in resulting tree */
+ sha1 = tree_entry_extract(t, &path, &mode);
+ pathlen = tree_entry_len(&t->entry);
+ isdir = S_ISDIR(mode);
+ } else {
+ /*
+ * a path was removed - take path from imin parent. Also take
+ * mode from that parent, to decide on recursion(1).
+ *
+ * 1) all modes for tp[i]=tp[imin] should be the same wrt
+ * S_ISDIR, thanks to base_name_compare().
+ */
+ tree_entry_extract(&tp[imin], &path, &mode);
+ pathlen = tree_entry_len(&tp[imin].entry);
- tree = read_sha1_file(sha1, &type, &size);
- if (!tree || type != OBJ_TREE)
- die("corrupt tree sha %s", sha1_to_hex(sha1));
+ isdir = S_ISDIR(mode);
+ sha1 = NULL;
+ mode = 0;
+ }
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
- opt->add_remove(opt, *prefix, mode, sha1, 1, base->buf, 0);
+ if (DIFF_OPT_TST(opt, RECURSIVE) && isdir) {
+ recurse = 1;
+ emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE);
+ }
- strbuf_addch(base, '/');
+ if (emitthis) {
+ int keep;
+ struct combine_diff_path *pprev = p;
+ p = path_appendnew(p, nparent, base, path, pathlen, mode, sha1);
- init_tree_desc(&inner, tree, size);
- show_tree(opt, prefix, &inner, base);
- free(tree);
- } else
- opt->add_remove(opt, prefix[0], mode, sha1, 1, base->buf, 0);
+ for (i = 0; i < nparent; ++i) {
+ /*
+ * tp[i] is valid, if present and if tp[i]==tp[imin] -
+ * otherwise, we should ignore it.
+ */
+ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ);
+
+ const unsigned char *sha1_i;
+ unsigned mode_i;
+
+ p->parent[i].status =
+ !t ? DIFF_STATUS_DELETED :
+ tpi_valid ?
+ DIFF_STATUS_MODIFIED :
+ DIFF_STATUS_ADDED;
+
+ if (tpi_valid) {
+ sha1_i = tp[i].entry.sha1;
+ mode_i = tp[i].entry.mode;
+ }
+ else {
+ sha1_i = NULL;
+ mode_i = 0;
+ }
+
+ p->parent[i].mode = mode_i;
+ hashcpy(p->parent[i].sha1, sha1_i ? sha1_i : null_sha1);
+ }
+
+ keep = 1;
+ if (opt->pathchange)
+ keep = opt->pathchange(opt, p);
+
+ /*
+ * If a path was filtered or consumed - we don't need to add it
+ * to the list and can reuse its memory, leaving it as
+ * pre-allocated element on the tail.
+ *
+ * On the other hand, if path needs to be kept, we need to
+ * correct its .next to NULL, as it was pre-initialized to how
+ * much memory was allocated.
+ *
+ * see path_appendnew() for details.
+ */
+ if (!keep)
+ p = pprev;
+ else
+ p->next = NULL;
+ }
+
+ if (recurse) {
+ const unsigned char **parents_sha1;
+
+ parents_sha1 = xalloca(nparent * sizeof(parents_sha1[0]));
+ for (i = 0; i < nparent; ++i) {
+ /* same rule as in emitthis */
+ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ);
+
+ parents_sha1[i] = tpi_valid ? tp[i].entry.sha1
+ : NULL;
+ }
+
+ strbuf_add(base, path, pathlen);
+ strbuf_addch(base, '/');
+ p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt);
+ xalloca_free(parents_sha1);
+ }
strbuf_setlen(base, old_baselen);
+ return p;
}
static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
- struct diff_options *opt,
- enum interesting *match)
+ struct diff_options *opt)
{
+ enum interesting match;
+
while (t->size) {
- *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
- if (*match) {
- if (*match == all_entries_not_interesting)
+ match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
+ if (match) {
+ if (match == all_entries_not_interesting)
t->size = 0;
break;
}
}
}
-int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
- const char *base_str, struct diff_options *opt)
+
+/*
+ * generate paths for combined diff D(sha1,parents_sha1[])
+ *
+ * Resulting paths are appended to combine_diff_path linked list, and also, are
+ * emitted on the go via opt->pathchange() callback, so it is possible to
+ * process the result as batch or incrementally.
+ *
+ * The paths are generated scanning new tree and all parents trees
+ * simultaneously, similarly to what diff_tree() was doing for 2 trees.
+ * The theory behind such scan is as follows:
+ *
+ *
+ * D(T,P1...Pn) calculation scheme
+ * -------------------------------
+ *
+ * D(T,P1...Pn) = D(T,P1) ^ ... ^ D(T,Pn) (regarding resulting paths set)
+ *
+ * D(T,Pj) - diff between T..Pj
+ * D(T,P1...Pn) - combined diff from T to parents P1,...,Pn
+ *
+ *
+ * We start from all trees, which are sorted, and compare their entries in
+ * lock-step:
+ *
+ * T P1 Pn
+ * - - -
+ * |t| |p1| |pn|
+ * |-| |--| ... |--| imin = argmin(p1...pn)
+ * | | | | | |
+ * |-| |--| |--|
+ * |.| |. | |. |
+ * . . .
+ * . . .
+ *
+ * at any time there could be 3 cases:
+ *
+ * 1) t < p[imin];
+ * 2) t > p[imin];
+ * 3) t = p[imin].
+ *
+ * Schematic deduction of what every case means, and what to do, follows:
+ *
+ * 1) t < p[imin] -> ∀j t ∉ Pj -> "+t" ∈ D(T,Pj) -> D += "+t"; t↓
+ *
+ * 2) t > p[imin]
+ *
+ * 2.1) ∃j: pj > p[imin] -> "-p[imin]" ∉ D(T,Pj) -> D += ø; ∀ pi=p[imin] pi↓
+ * 2.2) ∀i pi = p[imin] -> pi ∉ T -> "-pi" ∈ D(T,Pi) -> D += "-p[imin]"; ∀i pi↓
+ *
+ * 3) t = p[imin]
+ *
+ * 3.1) ∃j: pj > p[imin] -> "+t" ∈ D(T,Pj) -> only pi=p[imin] remains to investigate
+ * 3.2) pi = p[imin] -> investigate δ(t,pi)
+ * |
+ * |
+ * v
+ *
+ * 3.1+3.2) looking at δ(t,pi) ∀i: pi=p[imin] - if all != ø ->
+ *
+ * ⎧δ(t,pi) - if pi=p[imin]
+ * -> D += ⎨
+ * ⎩"+t" - if pi>p[imin]
+ *
+ *
+ * in any case t↓ ∀ pi=p[imin] pi↓
+ *
+ *
+ * ~~~~~~~~
+ *
+ * NOTE
+ *
+ * Usual diff D(A,B) is by definition the same as combined diff D(A,[B]),
+ * so this diff paths generator can, and is used, for plain diffs
+ * generation too.
+ *
+ * Please keep attention to the common D(A,[B]) case when working on the
+ * code, in order not to slow it down.
+ *
+ * NOTE
+ * nparent must be > 0.
+ */
+
+
+/* ∀ pi=p[imin] pi↓ */
+static inline void update_tp_entries(struct tree_desc *tp, int nparent)
{
- struct strbuf base;
- int baselen = strlen(base_str);
- enum interesting t1_match = entry_not_interesting;
- enum interesting t2_match = entry_not_interesting;
+ int i;
+ for (i = 0; i < nparent; ++i)
+ if (!(tp[i].entry.mode & S_IFXMIN_NEQ))
+ update_tree_entry(&tp[i]);
+}
+
+static struct combine_diff_path *ll_diff_tree_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parents_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt)
+{
+ struct tree_desc t, *tp;
+ void *ttree, **tptree;
+ int i;
+
+ tp = xalloca(nparent * sizeof(tp[0]));
+ tptree = xalloca(nparent * sizeof(tptree[0]));
+
+ /*
+ * load parents first, as they are probably already cached.
+ *
+ * ( log_tree_diff() parses commit->parent before calling here via
+ * diff_tree_sha1(parent, commit) )
+ */
+ for (i = 0; i < nparent; ++i)
+ tptree[i] = fill_tree_descriptor(&tp[i], parents_sha1[i]);
+ ttree = fill_tree_descriptor(&t, sha1);
/* Enable recursion indefinitely */
opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
- strbuf_init(&base, PATH_MAX);
- strbuf_add(&base, base_str, baselen);
-
for (;;) {
+ int imin, cmp;
+
if (diff_can_quit_early(opt))
break;
+
if (opt->pathspec.nr) {
- skip_uninteresting(t1, &base, opt, &t1_match);
- skip_uninteresting(t2, &base, opt, &t2_match);
+ skip_uninteresting(&t, base, opt);
+ for (i = 0; i < nparent; i++)
+ skip_uninteresting(&tp[i], base, opt);
}
- if (!t1->size) {
- if (!t2->size)
+
+ /* comparing is finished when all trees are done */
+ if (!t.size) {
+ int done = 1;
+ for (i = 0; i < nparent; ++i)
+ if (tp[i].size) {
+ done = 0;
+ break;
+ }
+ if (done)
break;
- show_entry(opt, "+", t2, &base);
- update_tree_entry(t2);
- continue;
}
- if (!t2->size) {
- show_entry(opt, "-", t1, &base);
- update_tree_entry(t1);
- continue;
+
+ /*
+ * lookup imin = argmin(p1...pn),
+ * mark entries whether they =p[imin] along the way
+ */
+ imin = 0;
+ tp[0].entry.mode &= ~S_IFXMIN_NEQ;
+
+ for (i = 1; i < nparent; ++i) {
+ cmp = tree_entry_pathcmp(&tp[i], &tp[imin]);
+ if (cmp < 0) {
+ imin = i;
+ tp[i].entry.mode &= ~S_IFXMIN_NEQ;
+ }
+ else if (cmp == 0) {
+ tp[i].entry.mode &= ~S_IFXMIN_NEQ;
+ }
+ else {
+ tp[i].entry.mode |= S_IFXMIN_NEQ;
+ }
+ }
+
+ /* fixup markings for entries before imin */
+ for (i = 0; i < imin; ++i)
+ tp[i].entry.mode |= S_IFXMIN_NEQ; /* pi > p[imin] */
+
+
+
+ /* compare t vs p[imin] */
+ cmp = tree_entry_pathcmp(&t, &tp[imin]);
+
+ /* t = p[imin] */
+ if (cmp == 0) {
+ /* are either pi > p[imin] or diff(t,pi) != ø ? */
+ if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
+ for (i = 0; i < nparent; ++i) {
+ /* p[i] > p[imin] */
+ if (tp[i].entry.mode & S_IFXMIN_NEQ)
+ continue;
+
+ /* diff(t,pi) != ø */
+ if (hashcmp(t.entry.sha1, tp[i].entry.sha1) ||
+ (t.entry.mode != tp[i].entry.mode))
+ continue;
+
+ goto skip_emit_t_tp;
+ }
+ }
+
+ /* D += {δ(t,pi) if pi=p[imin]; "+a" if pi > p[imin]} */
+ p = emit_path(p, base, opt, nparent,
+ &t, tp, imin);
+
+ skip_emit_t_tp:
+ /* t↓, ∀ pi=p[imin] pi↓ */
+ update_tree_entry(&t);
+ update_tp_entries(tp, nparent);
+ }
+
+ /* t < p[imin] */
+ else if (cmp < 0) {
+ /* D += "+t" */
+ p = emit_path(p, base, opt, nparent,
+ &t, /*tp=*/NULL, -1);
+
+ /* t↓ */
+ update_tree_entry(&t);
}
- switch (compare_tree_entry(t1, t2, &base, opt)) {
- case -1:
- update_tree_entry(t1);
- continue;
- case 0:
- update_tree_entry(t1);
- /* Fallthrough */
- case 1:
- update_tree_entry(t2);
- continue;
+
+ /* t > p[imin] */
+ else {
+ /* ∀i pi=p[imin] -> D += "-p[imin]" */
+ if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
+ for (i = 0; i < nparent; ++i)
+ if (tp[i].entry.mode & S_IFXMIN_NEQ)
+ goto skip_emit_tp;
+ }
+
+ p = emit_path(p, base, opt, nparent,
+ /*t=*/NULL, tp, imin);
+
+ skip_emit_tp:
+ /* ∀ pi=p[imin] pi↓ */
+ update_tp_entries(tp, nparent);
}
- die("git diff-tree: internal error");
}
- strbuf_release(&base);
- return 0;
+ free(ttree);
+ for (i = nparent-1; i >= 0; i--)
+ free(tptree[i]);
+ xalloca_free(tptree);
+ xalloca_free(tp);
+
+ return p;
+}
+
+struct combine_diff_path *diff_tree_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parents_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt)
+{
+ p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt);
+
+ /*
+ * free pre-allocated last element, if any
+ * (see path_appendnew() for details about why)
+ */
+ if (p->next) {
+ free(p->next);
+ p->next = NULL;
+ }
+
+ return p;
}
/*
!DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
}
-static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+static void try_to_follow_renames(const unsigned char *old, const unsigned char *new, struct strbuf *base, struct diff_options *opt)
{
struct diff_options diff_opts;
struct diff_queue_struct *q = &diff_queued_diff;
diff_opts.break_opt = opt->break_opt;
diff_opts.rename_score = opt->rename_score;
diff_setup_done(&diff_opts);
- diff_tree(t1, t2, base, &diff_opts);
+ ll_diff_tree_sha1(old, new, base, &diff_opts);
diffcore_std(&diff_opts);
free_pathspec(&diff_opts.pathspec);
q->nr = 1;
}
-int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
+static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+ struct strbuf *base, struct diff_options *opt)
{
- void *tree1, *tree2;
- struct tree_desc t1, t2;
- unsigned long size1, size2;
- int retval;
+ struct combine_diff_path phead, *p;
+ pathchange_fn_t pathchange_old = opt->pathchange;
- tree1 = fill_tree_descriptor(&t1, old);
- tree2 = fill_tree_descriptor(&t2, new);
- size1 = t1.size;
- size2 = t2.size;
- retval = diff_tree(&t1, &t2, base, opt);
- if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
- init_tree_desc(&t1, tree1, size1);
- init_tree_desc(&t2, tree2, size2);
- try_to_follow_renames(&t1, &t2, base, opt);
+ phead.next = NULL;
+ opt->pathchange = emit_diff_first_parent_only;
+ diff_tree_paths(&phead, new, &old, 1, base, opt);
+
+ for (p = phead.next; p;) {
+ struct combine_diff_path *pprev = p;
+ p = p->next;
+ free(pprev);
}
- free(tree1);
- free(tree2);
+
+ opt->pathchange = pathchange_old;
+ return 0;
+}
+
+int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base_str, struct diff_options *opt)
+{
+ struct strbuf base;
+ int retval;
+
+ strbuf_init(&base, PATH_MAX);
+ strbuf_addstr(&base, base_str);
+
+ retval = ll_diff_tree_sha1(old, new, &base, opt);
+ if (!*base_str && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename())
+ try_to_follow_renames(old, new, &base, opt);
+
+ strbuf_release(&base);
+
return retval;
}
--- /dev/null
+static const struct interval zero_width[] = {
+{ 0x0300, 0x036F },
+{ 0x0483, 0x0489 },
+{ 0x0591, 0x05BD },
+{ 0x05BF, 0x05BF },
+{ 0x05C1, 0x05C2 },
+{ 0x05C4, 0x05C5 },
+{ 0x05C7, 0x05C7 },
+{ 0x0600, 0x0604 },
+{ 0x0610, 0x061A },
+{ 0x061C, 0x061C },
+{ 0x064B, 0x065F },
+{ 0x0670, 0x0670 },
+{ 0x06D6, 0x06DD },
+{ 0x06DF, 0x06E4 },
+{ 0x06E7, 0x06E8 },
+{ 0x06EA, 0x06ED },
+{ 0x070F, 0x070F },
+{ 0x0711, 0x0711 },
+{ 0x0730, 0x074A },
+{ 0x07A6, 0x07B0 },
+{ 0x07EB, 0x07F3 },
+{ 0x0816, 0x0819 },
+{ 0x081B, 0x0823 },
+{ 0x0825, 0x0827 },
+{ 0x0829, 0x082D },
+{ 0x0859, 0x085B },
+{ 0x08E4, 0x08FE },
+{ 0x0900, 0x0902 },
+{ 0x093A, 0x093A },
+{ 0x093C, 0x093C },
+{ 0x0941, 0x0948 },
+{ 0x094D, 0x094D },
+{ 0x0951, 0x0957 },
+{ 0x0962, 0x0963 },
+{ 0x0981, 0x0981 },
+{ 0x09BC, 0x09BC },
+{ 0x09C1, 0x09C4 },
+{ 0x09CD, 0x09CD },
+{ 0x09E2, 0x09E3 },
+{ 0x0A01, 0x0A02 },
+{ 0x0A3C, 0x0A3C },
+{ 0x0A41, 0x0A42 },
+{ 0x0A47, 0x0A48 },
+{ 0x0A4B, 0x0A4D },
+{ 0x0A51, 0x0A51 },
+{ 0x0A70, 0x0A71 },
+{ 0x0A75, 0x0A75 },
+{ 0x0A81, 0x0A82 },
+{ 0x0ABC, 0x0ABC },
+{ 0x0AC1, 0x0AC5 },
+{ 0x0AC7, 0x0AC8 },
+{ 0x0ACD, 0x0ACD },
+{ 0x0AE2, 0x0AE3 },
+{ 0x0B01, 0x0B01 },
+{ 0x0B3C, 0x0B3C },
+{ 0x0B3F, 0x0B3F },
+{ 0x0B41, 0x0B44 },
+{ 0x0B4D, 0x0B4D },
+{ 0x0B56, 0x0B56 },
+{ 0x0B62, 0x0B63 },
+{ 0x0B82, 0x0B82 },
+{ 0x0BC0, 0x0BC0 },
+{ 0x0BCD, 0x0BCD },
+{ 0x0C3E, 0x0C40 },
+{ 0x0C46, 0x0C48 },
+{ 0x0C4A, 0x0C4D },
+{ 0x0C55, 0x0C56 },
+{ 0x0C62, 0x0C63 },
+{ 0x0CBC, 0x0CBC },
+{ 0x0CBF, 0x0CBF },
+{ 0x0CC6, 0x0CC6 },
+{ 0x0CCC, 0x0CCD },
+{ 0x0CE2, 0x0CE3 },
+{ 0x0D41, 0x0D44 },
+{ 0x0D4D, 0x0D4D },
+{ 0x0D62, 0x0D63 },
+{ 0x0DCA, 0x0DCA },
+{ 0x0DD2, 0x0DD4 },
+{ 0x0DD6, 0x0DD6 },
+{ 0x0E31, 0x0E31 },
+{ 0x0E34, 0x0E3A },
+{ 0x0E47, 0x0E4E },
+{ 0x0EB1, 0x0EB1 },
+{ 0x0EB4, 0x0EB9 },
+{ 0x0EBB, 0x0EBC },
+{ 0x0EC8, 0x0ECD },
+{ 0x0F18, 0x0F19 },
+{ 0x0F35, 0x0F35 },
+{ 0x0F37, 0x0F37 },
+{ 0x0F39, 0x0F39 },
+{ 0x0F71, 0x0F7E },
+{ 0x0F80, 0x0F84 },
+{ 0x0F86, 0x0F87 },
+{ 0x0F8D, 0x0F97 },
+{ 0x0F99, 0x0FBC },
+{ 0x0FC6, 0x0FC6 },
+{ 0x102D, 0x1030 },
+{ 0x1032, 0x1037 },
+{ 0x1039, 0x103A },
+{ 0x103D, 0x103E },
+{ 0x1058, 0x1059 },
+{ 0x105E, 0x1060 },
+{ 0x1071, 0x1074 },
+{ 0x1082, 0x1082 },
+{ 0x1085, 0x1086 },
+{ 0x108D, 0x108D },
+{ 0x109D, 0x109D },
+{ 0x1160, 0x11FF },
+{ 0x135D, 0x135F },
+{ 0x1712, 0x1714 },
+{ 0x1732, 0x1734 },
+{ 0x1752, 0x1753 },
+{ 0x1772, 0x1773 },
+{ 0x17B4, 0x17B5 },
+{ 0x17B7, 0x17BD },
+{ 0x17C6, 0x17C6 },
+{ 0x17C9, 0x17D3 },
+{ 0x17DD, 0x17DD },
+{ 0x180B, 0x180E },
+{ 0x18A9, 0x18A9 },
+{ 0x1920, 0x1922 },
+{ 0x1927, 0x1928 },
+{ 0x1932, 0x1932 },
+{ 0x1939, 0x193B },
+{ 0x1A17, 0x1A18 },
+{ 0x1A1B, 0x1A1B },
+{ 0x1A56, 0x1A56 },
+{ 0x1A58, 0x1A5E },
+{ 0x1A60, 0x1A60 },
+{ 0x1A62, 0x1A62 },
+{ 0x1A65, 0x1A6C },
+{ 0x1A73, 0x1A7C },
+{ 0x1A7F, 0x1A7F },
+{ 0x1B00, 0x1B03 },
+{ 0x1B34, 0x1B34 },
+{ 0x1B36, 0x1B3A },
+{ 0x1B3C, 0x1B3C },
+{ 0x1B42, 0x1B42 },
+{ 0x1B6B, 0x1B73 },
+{ 0x1B80, 0x1B81 },
+{ 0x1BA2, 0x1BA5 },
+{ 0x1BA8, 0x1BA9 },
+{ 0x1BAB, 0x1BAB },
+{ 0x1BE6, 0x1BE6 },
+{ 0x1BE8, 0x1BE9 },
+{ 0x1BED, 0x1BED },
+{ 0x1BEF, 0x1BF1 },
+{ 0x1C2C, 0x1C33 },
+{ 0x1C36, 0x1C37 },
+{ 0x1CD0, 0x1CD2 },
+{ 0x1CD4, 0x1CE0 },
+{ 0x1CE2, 0x1CE8 },
+{ 0x1CED, 0x1CED },
+{ 0x1CF4, 0x1CF4 },
+{ 0x1DC0, 0x1DE6 },
+{ 0x1DFC, 0x1DFF },
+{ 0x200B, 0x200F },
+{ 0x202A, 0x202E },
+{ 0x2060, 0x2064 },
+{ 0x2066, 0x206F },
+{ 0x20D0, 0x20F0 },
+{ 0x2CEF, 0x2CF1 },
+{ 0x2D7F, 0x2D7F },
+{ 0x2DE0, 0x2DFF },
+{ 0x302A, 0x302D },
+{ 0x3099, 0x309A },
+{ 0xA66F, 0xA672 },
+{ 0xA674, 0xA67D },
+{ 0xA69F, 0xA69F },
+{ 0xA6F0, 0xA6F1 },
+{ 0xA802, 0xA802 },
+{ 0xA806, 0xA806 },
+{ 0xA80B, 0xA80B },
+{ 0xA825, 0xA826 },
+{ 0xA8C4, 0xA8C4 },
+{ 0xA8E0, 0xA8F1 },
+{ 0xA926, 0xA92D },
+{ 0xA947, 0xA951 },
+{ 0xA980, 0xA982 },
+{ 0xA9B3, 0xA9B3 },
+{ 0xA9B6, 0xA9B9 },
+{ 0xA9BC, 0xA9BC },
+{ 0xAA29, 0xAA2E },
+{ 0xAA31, 0xAA32 },
+{ 0xAA35, 0xAA36 },
+{ 0xAA43, 0xAA43 },
+{ 0xAA4C, 0xAA4C },
+{ 0xAAB0, 0xAAB0 },
+{ 0xAAB2, 0xAAB4 },
+{ 0xAAB7, 0xAAB8 },
+{ 0xAABE, 0xAABF },
+{ 0xAAC1, 0xAAC1 },
+{ 0xAAEC, 0xAAED },
+{ 0xAAF6, 0xAAF6 },
+{ 0xABE5, 0xABE5 },
+{ 0xABE8, 0xABE8 },
+{ 0xABED, 0xABED },
+{ 0xFB1E, 0xFB1E },
+{ 0xFE00, 0xFE0F },
+{ 0xFE20, 0xFE26 },
+{ 0xFEFF, 0xFEFF },
+{ 0xFFF9, 0xFFFB },
+{ 0x101FD, 0x101FD },
+{ 0x10A01, 0x10A03 },
+{ 0x10A05, 0x10A06 },
+{ 0x10A0C, 0x10A0F },
+{ 0x10A38, 0x10A3A },
+{ 0x10A3F, 0x10A3F },
+{ 0x11001, 0x11001 },
+{ 0x11038, 0x11046 },
+{ 0x11080, 0x11081 },
+{ 0x110B3, 0x110B6 },
+{ 0x110B9, 0x110BA },
+{ 0x110BD, 0x110BD },
+{ 0x11100, 0x11102 },
+{ 0x11127, 0x1112B },
+{ 0x1112D, 0x11134 },
+{ 0x11180, 0x11181 },
+{ 0x111B6, 0x111BE },
+{ 0x116AB, 0x116AB },
+{ 0x116AD, 0x116AD },
+{ 0x116B0, 0x116B5 },
+{ 0x116B7, 0x116B7 },
+{ 0x16F8F, 0x16F92 },
+{ 0x1D167, 0x1D169 },
+{ 0x1D173, 0x1D182 },
+{ 0x1D185, 0x1D18B },
+{ 0x1D1AA, 0x1D1AD },
+{ 0x1D242, 0x1D244 },
+{ 0xE0001, 0xE0001 },
+{ 0xE0020, 0xE007F },
+{ 0xE0100, 0xE01EF }
+};
+static const struct interval double_width[] = {
+{ /* plane */ 0x0, 0x1C },
+{ /* plane */ 0x1C, 0x21 },
+{ /* plane */ 0x21, 0x22 },
+{ /* plane */ 0x22, 0x23 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ /* plane */ 0x0, 0x0 },
+{ 0x1100, 0x115F },
+{ 0x2329, 0x232A },
+{ 0x2E80, 0x2E99 },
+{ 0x2E9B, 0x2EF3 },
+{ 0x2F00, 0x2FD5 },
+{ 0x2FF0, 0x2FFB },
+{ 0x3000, 0x303E },
+{ 0x3041, 0x3096 },
+{ 0x3099, 0x30FF },
+{ 0x3105, 0x312D },
+{ 0x3131, 0x318E },
+{ 0x3190, 0x31BA },
+{ 0x31C0, 0x31E3 },
+{ 0x31F0, 0x321E },
+{ 0x3220, 0x3247 },
+{ 0x3250, 0x32FE },
+{ 0x3300, 0x4DBF },
+{ 0x4E00, 0xA48C },
+{ 0xA490, 0xA4C6 },
+{ 0xA960, 0xA97C },
+{ 0xAC00, 0xD7A3 },
+{ 0xF900, 0xFAFF },
+{ 0xFE10, 0xFE19 },
+{ 0xFE30, 0xFE52 },
+{ 0xFE54, 0xFE66 },
+{ 0xFE68, 0xFE6B },
+{ 0xFF01, 0xFF60 },
+{ 0xFFE0, 0xFFE6 },
+{ 0x1B000, 0x1B001 },
+{ 0x1F200, 0x1F202 },
+{ 0x1F210, 0x1F23A },
+{ 0x1F240, 0x1F248 },
+{ 0x1F250, 0x1F251 },
+{ 0x20000, 0x2FFFD },
+{ 0x30000, 0x3FFFD }
+};
--- /dev/null
+#!/bin/sh
+#See http://www.unicode.org/reports/tr44/
+#
+#Me Enclosing_Mark an enclosing combining mark
+#Mn Nonspacing_Mark a nonspacing combining mark (zero advance width)
+#Cf Format a format control character
+#
+UNICODEWIDTH_H=../unicode_width.h
+if ! test -d unicode; then
+ mkdir unicode
+fi &&
+( cd unicode &&
+ if ! test -f UnicodeData.txt; then
+ wget http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
+ fi &&
+ if ! test -f EastAsianWidth.txt; then
+ wget http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
+ fi &&
+ if ! test -d uniset; then
+ git clone https://github.com/depp/uniset.git
+ fi &&
+ (
+ cd uniset &&
+ if ! test -x uniset; then
+ autoreconf -i &&
+ ./configure --enable-warnings=-Werror CFLAGS='-O0 -ggdb'
+ fi &&
+ make
+ ) &&
+ echo "static const struct interval zero_width[] = {" >$UNICODEWIDTH_H &&
+ UNICODE_DIR=. ./uniset/uniset --32 cat:Me,Mn,Cf + U+1160..U+11FF - U+00AD |
+ grep -v plane >>$UNICODEWIDTH_H &&
+ echo "};" >>$UNICODEWIDTH_H &&
+ echo "static const struct interval double_width[] = {" >>$UNICODEWIDTH_H &&
+ UNICODE_DIR=. ./uniset/uniset --32 eaw:F,W >>$UNICODEWIDTH_H &&
+ echo "};" >>$UNICODEWIDTH_H
+)
{
/*
* Sorted list of non-overlapping intervals of non-spacing characters,
- * generated by
- * "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
*/
- static const struct interval combining[] = {
- { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
- { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
- { 0x05C7, 0x05C7 }, { 0x0600, 0x0604 }, { 0x0610, 0x061A },
- { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
- { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
- { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
- { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
- { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
- { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
- { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
- { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
- { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
- { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
- { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 },
- { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
- { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
- { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
- { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
- { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
- { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
- { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
- { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
- { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
- { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
- { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
- { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
- { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
- { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
- { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 },
- { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
- { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
- { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
- { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
- { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F },
- { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F },
- { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
- { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 },
- { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 },
- { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
- { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
- { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
- };
+#include "unicode_width.h"
/* test for 8-bit control characters */
if (ch == 0)
return -1;
/* binary search in table of non-spacing characters */
- if (bisearch(ch, combining, sizeof(combining)
+ if (bisearch(ch, zero_width, sizeof(zero_width)
/ sizeof(struct interval) - 1))
return 0;
- /*
- * If we arrive here, ch is neither a combining nor a C0/C1
- * control character.
- */
+ /* binary search in table of double width characters */
+ if (bisearch(ch, double_width, sizeof(double_width)
+ / sizeof(struct interval) - 1))
+ return 2;
- return 1 +
- (ch >= 0x1100 &&
- /* Hangul Jamo init. consonants */
- (ch <= 0x115f ||
- ch == 0x2329 || ch == 0x232a ||
- /* CJK ... Yi */
- (ch >= 0x2e80 && ch <= 0xa4cf &&
- ch != 0x303f) ||
- /* Hangul Syllables */
- (ch >= 0xac00 && ch <= 0xd7a3) ||
- /* CJK Compatibility Ideographs */
- (ch >= 0xf900 && ch <= 0xfaff) ||
- /* CJK Compatibility Forms */
- (ch >= 0xfe30 && ch <= 0xfe6f) ||
- /* Fullwidth Forms */
- (ch >= 0xff00 && ch <= 0xff60) ||
- (ch >= 0xffe0 && ch <= 0xffe6) ||
- (ch >= 0x20000 && ch <= 0x2fffd) ||
- (ch >= 0x30000 && ch <= 0x3fffd)));
+ return 1;
}
/*
}
}
+/*
+ * xpread() is the same as pread(), but it automatically restarts pread()
+ * operations with a recoverable error (EAGAIN and EINTR). xpread() DOES
+ * NOT GUARANTEE that "len" bytes is read even if the data is available.
+ */
+ssize_t xpread(int fd, void *buf, size_t len, off_t offset)
+{
+ ssize_t nr;
+ if (len > MAX_IO_SIZE)
+ len = MAX_IO_SIZE;
+ while (1) {
+ nr = pread(fd, buf, len, offset);
+ if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+ continue;
+ return nr;
+ }
+}
+
ssize_t read_in_full(int fd, void *buf, size_t count)
{
char *p = buf;
return total;
}
+ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset)
+{
+ char *p = buf;
+ ssize_t total = 0;
+
+ while (count > 0) {
+ ssize_t loaded = xpread(fd, p, count, offset);
+ if (loaded < 0)
+ return -1;
+ if (loaded == 0)
+ return total;
+ count -= loaded;
+ p += loaded;
+ total += loaded;
+ offset += loaded;
+ }
+
+ return total;
+}
+
int xdup(int fd)
{
int ret = dup(fd);
} else {
status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
}
- status_printf_ln(s, c, "");
+ status_printf_ln(s, c, "%s", "");
}
static void wt_status_print_cached_header(struct wt_status *s)
status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference);
else
status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)"));
- status_printf_ln(s, c, "");
+ status_printf_ln(s, c, "%s", "");
}
static void wt_status_print_dirty_header(struct wt_status *s,
status_printf_ln(s, c, _(" (use \"git checkout -- <file>...\" to discard changes in working directory)"));
if (has_dirty_submodules)
status_printf_ln(s, c, _(" (commit or discard the untracked or modified content in submodules)"));
- status_printf_ln(s, c, "");
+ status_printf_ln(s, c, "%s", "");
}
static void wt_status_print_other_header(struct wt_status *s,
if (!s->hints)
return;
status_printf_ln(s, c, _(" (use \"git %s <file>...\" to include in what will be committed)"), how);
- status_printf_ln(s, c, "");
+ status_printf_ln(s, c, "%s", "");
}
static void wt_status_print_trailer(struct wt_status *s)
{
- status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
}
#define quote_path quote_path_relative
string_list_clear(&output, 0);
strbuf_release(&buf);
conclude:
- status_printf_ln(s, GIT_COLOR_NORMAL, "");
+ status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
}
void wt_status_truncate_message_at_cut_line(struct strbuf *buf)
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
comment_line_char);
else
- fprintf_ln(s->fp, "");
+ fputs("", s->fp);
}
static int has_unmerged(struct wt_status *s)
on_what = _("Not currently on any branch.");
}
}
- status_printf(s, color(WT_STATUS_HEADER, s), "");
+ status_printf(s, color(WT_STATUS_HEADER, s), "%s", "");
status_printf_more(s, branch_status_color, "%s", on_what);
status_printf_more(s, branch_color, "%s\n", branch_name);
if (!s->is_initial)
free(state.detached_from);
if (s->is_initial) {
- status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
- status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
}
wt_status_print_updated(s);
if (s->show_ignored_files)
wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
if (advice_status_u_option && 2000 < s->untracked_in_ms) {
- status_printf_ln(s, GIT_COLOR_NORMAL, "");
+ status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
status_printf_ln(s, GIT_COLOR_NORMAL,
_("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
"may speed it up, but you have to be careful not to forget to add\n"