mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
$(QUIET_GEN) \
- $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
+ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && TOOL_MODE=diff && \
. ../git-mergetool--lib.sh && \
- show_tool_names can_diff "* " || :' >mergetools-diff.txt && \
- $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
+ show_tool_names can_diff' | sed -e "s/\([a-z0-9]*\)/\`\1\`;;/" >mergetools-diff.txt && \
+ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && TOOL_MODE=merge && \
. ../git-mergetool--lib.sh && \
- show_tool_names can_merge "* " || :' >mergetools-merge.txt && \
+ show_tool_names can_merge' | sed -e "s/\([a-z0-9]*\)/\`\1\`;;/" >mergetools-merge.txt && \
date >$@
TRACK_ASCIIDOCFLAGS = $(subst ','\'',$(ASCIIDOC_COMMON):$(ASCIIDOC_HTML):$(ASCIIDOC_DOCBOOK))
Go ahead and commit this change, as well.
[[ready-to-share]]
-== Getting Ready to Share
+== Getting Ready to Share: Anatomy of a Patch Series
You may have noticed already that the Git project performs its code reviews via
emailed patches, which are then applied by the maintainer when they are ready
-and approved by the community. The Git project does not accept patches from
+and approved by the community. The Git project does not accept contributions from
pull requests, and the patches emailed for review need to be formatted a
-specific way. At this point the tutorial diverges, in order to demonstrate two
+specific way.
+
+:patch-series: https://lore.kernel.org/git/pull.1218.git.git.1645209647.gitgitgadget@gmail.com/
+:lore: https://lore.kernel.org/git/
+
+Before taking a look at how to convert your commits into emailed patches,
+let's analyze what the end result, a "patch series", looks like. Here is an
+{patch-series}[example] of the summary view for a patch series on the web interface of
+the {lore}[Git mailing list archive]:
+
+----
+2022-02-18 18:40 [PATCH 0/3] libify reflog John Cai via GitGitGadget
+2022-02-18 18:40 ` [PATCH 1/3] reflog: libify delete reflog function and helpers John Cai via GitGitGadget
+2022-02-18 19:10 ` Ævar Arnfjörð Bjarmason [this message]
+2022-02-18 19:39 ` Taylor Blau
+2022-02-18 19:48 ` Ævar Arnfjörð Bjarmason
+2022-02-18 19:35 ` Taylor Blau
+2022-02-21 1:43 ` John Cai
+2022-02-21 1:50 ` Taylor Blau
+2022-02-23 19:50 ` John Cai
+2022-02-18 20:00 ` // other replies ellided
+2022-02-18 18:40 ` [PATCH 2/3] reflog: call reflog_delete from reflog.c John Cai via GitGitGadget
+2022-02-18 19:15 ` Ævar Arnfjörð Bjarmason
+2022-02-18 20:26 ` Junio C Hamano
+2022-02-18 18:40 ` [PATCH 3/3] stash: call reflog_delete from reflog.c John Cai via GitGitGadget
+2022-02-18 19:20 ` Ævar Arnfjörð Bjarmason
+2022-02-19 0:21 ` Taylor Blau
+2022-02-22 2:36 ` John Cai
+2022-02-22 10:51 ` Ævar Arnfjörð Bjarmason
+2022-02-18 19:29 ` [PATCH 0/3] libify reflog Ævar Arnfjörð Bjarmason
+2022-02-22 18:30 ` [PATCH v2 0/3] libify reflog John Cai via GitGitGadget
+2022-02-22 18:30 ` [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior John Cai via GitGitGadget
+2022-02-23 8:54 ` Ævar Arnfjörð Bjarmason
+2022-02-23 21:27 ` Junio C Hamano
+// continued
+----
+
+We can note a few things:
+
+- Each commit is sent as a separate email, with the commit message title as
+ subject, prefixed with "[PATCH _i_/_n_]" for the _i_-th commit of an
+ _n_-commit series.
+- Each patch is sent as a reply to an introductory email called the _cover
+ letter_ of the series, prefixed "[PATCH 0/_n_]".
+- Subsequent iterations of the patch series are labelled "PATCH v2", "PATCH
+ v3", etc. in place of "PATCH". For example, "[PATCH v2 1/3]" would be the first of
+ three patches in the second iteration. Each iteration is sent with a new cover
+ letter (like "[PATCH v2 0/3]" above), itself a reply to the cover letter of the
+ previous iteration (more on that below).
+
+NOTE: A single-patch topic is sent with "[PATCH]", "[PATCH v2]", etc. without
+_i_/_n_ numbering (in the above thread overview, no single-patch topic appears,
+though).
+
+[[cover-letter]]
+=== The cover letter
+
+In addition to an email per patch, the Git community also expects your patches
+to come with a cover letter. This is an important component of change
+submission as it explains to the community from a high level what you're trying
+to do, and why, in a way that's more apparent than just looking at your
+patches.
+
+The title of your cover letter should be something which succinctly covers the
+purpose of your entire topic branch. It's often in the imperative mood, just
+like our commit message titles. Here is how we'll title our series:
+
+---
+Add the 'psuh' command
+---
+
+The body of the cover letter is used to give additional context to reviewers.
+Be sure to explain anything your patches don't make clear on their own, but
+remember that since the cover letter is not recorded in the commit history,
+anything that might be useful to future readers of the repository's history
+should also be in your commit messages.
+
+Here's an example body for `psuh`:
+
+----
+Our internal metrics indicate widespread interest in the command
+git-psuh - that is, many users are trying to use it, but finding it is
+unavailable, using some unknown workaround instead.
+
+The following handful of patches add the psuh command and implement some
+handy features on top of it.
+
+This patchset is part of the MyFirstContribution tutorial and should not
+be merged.
+----
+
+At this point the tutorial diverges, in order to demonstrate two
different methods of formatting your patchset and getting it reviewed.
The first method to be covered is GitGitGadget, which is useful for those
request" button or the convenient "Compare & pull request" button that may
appear with the name of your newly pushed branch.
-Review the PR's title and description, as it's used by GitGitGadget as the cover
-letter for your change. When you're happy, submit your pull request.
+Review the PR's title and description, as they're used by GitGitGadget
+respectively as the subject and body of the cover letter for your change. Refer
+to <<cover-letter,"The cover letter">> above for advice on how to title your
+submission and what content to include in the description.
+
+NOTE: For single-patch contributions, your commit message should already be
+meaningful and explain at a high level the purpose (what is happening and why)
+of your patch, so you usually do not need any additional context. In that case,
+remove the PR description that GitHub automatically generates from your commit
+message (your PR description should be empty). If you do need to supply even
+more context, you can do so in that space and it will be appended to the email
+that GitGitGadget will send, between the three-dash line and the diffstat
+(see <<single-patch,Bonus Chapter: One-Patch Changes>> for how this looks once
+submitted).
+
+When you're happy, submit your pull request.
[[run-ci-ggg]]
=== Running CI and Getting Ready to Send
Check and make sure that your patches and cover letter template exist in the
directory you specified - you're nearly ready to send out your review!
-[[cover-letter]]
+[[preparing-cover-letter]]
=== Preparing Email
-In addition to an email per patch, the Git community also expects your patches
-to come with a cover letter, typically with a subject line [PATCH 0/x] (where
-x is the number of patches you're sending). Since you invoked `format-patch`
-with `--cover-letter`, you've already got a template ready. Open it up in your
-favorite editor.
+Since you invoked `format-patch` with `--cover-letter`, you've already got a
+cover letter template ready. Open it up in your favorite editor.
You should see a number of headers present already. Check that your `From:`
-header is correct. Then modify your `Subject:` to something which succinctly
-covers the purpose of your entire topic branch, for example:
+header is correct. Then modify your `Subject:` (see <<cover-letter,above>> for
+how to choose good title for your patch series):
----
-Subject: [PATCH 0/7] adding the 'psuh' command
+Subject: [PATCH 0/7] Add the 'psuh' command
----
Make sure you retain the ``[PATCH 0/X]'' part; that's what indicates to the Git
-community that this email is the beginning of a review, and many reviewers
-filter their email for this type of flag.
+community that this email is the beginning of a patch series, and many
+reviewers filter their email for this type of flag.
You'll need to add some extra parameters when you invoke `git send-email` to add
the cover letter.
-Next you'll have to fill out the body of your cover letter. This is an important
-component of change submission as it explains to the community from a high level
-what you're trying to do, and why, in a way that's more apparent than just
-looking at your diff. Be sure to explain anything your diff doesn't make clear
-on its own.
-
-Here's an example body for `psuh`:
-
-----
-Our internal metrics indicate widespread interest in the command
-git-psuh - that is, many users are trying to use it, but finding it is
-unavailable, using some unknown workaround instead.
-
-The following handful of patches add the psuh command and implement some
-handy features on top of it.
-
-This patchset is part of the MyFirstContribution tutorial and should not
-be merged.
-----
+Next you'll have to fill out the body of your cover letter. Again, see
+<<cover-letter,above>> for what content to include.
The template created by `git format-patch --cover-letter` includes a diffstat.
This gives reviewers a summary of what they're in for when reviewing your topic.
--- /dev/null
+Git v2.37 Release Notes
+=======================
+
+UI, Workflows & Features
+
+ * "vimdiff[123]" mergetool drivers have been reimplemented with a
+ more generic layout mechanism.
+
+ * "git -v" and "git -h" are now understood as "git --version" and
+ "git --help".
+
+ * The temporary files fed to external diff command are now generated
+ inside a new temporary directory under the same basename.
+
+ * "git log --since=X" will stop traversal upon seeing a commit that
+ is older than X, but there may be commits behind it that is younger
+ than X when the commit was created with a faulty clock. A new
+ option is added to keep digging without stopping, and instead
+ filter out commits with timestamp older than X.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The performance of the "untracked cache" feature has been improved
+ when "--untracked-files=<mode>" and "status.showUntrackedFiles"
+ are combined.
+
+ * "git stash" works better with sparse index entries.
+
+ * "git show :<path>" learned to work better with the sparse-index
+ feature.
+
+ * Introduce and apply coccinelle rule to discourage an explicit
+ comparison between a pointer and NULL, and applies the clean-up to
+ the maintenance track.
+
+Fixes since v2.36
+-----------------
+
+ * "git submodule update" without pathspec should silently skip an
+ uninitialized submodule, but it started to become noisy by mistake.
+ (merge 4f1ccef87c gc/submodule-update-part2 later to maint).
+
+ * "diff-tree --stdin" has been broken for about a year, but 2.36
+ release broke it even worse by breaking running the command with
+ <pathspec>, which in turn broke "gitk" and got noticed. This has
+ been corrected by aligning its behaviour to that of "log".
+ (merge f8781bfda3 jc/diff-tree-stdin-fix later to maint).
+
+ * Regression fix for 2.36 where "git name-rev" started to sometimes
+ reference strings after they are freed.
+ (merge 45a14f578e rs/name-rev-fix-free-after-use later to maint).
+
+ * "git show <commit1> <commit2>... -- <pathspec>" lost the pathspec
+ when showing the second and subsequent commits, which has been
+ corrected.
+ (merge 5cdb38458e jc/show-pathspec-fix later to maint).
+
+ * "git fast-export -- <pathspec>" lost the pathspec when showing the
+ second and subsequent commits, which has been corrected.
+ (merge d1c25272f5 rs/fast-export-pathspec-fix later to maint).
+
+ * "git format-patch <args> -- <pathspec>" lost the pathspec when
+ showing the second and subsequent commits, which has been
+ corrected.
+ (merge 91f8f7e46f rs/format-patch-pathspec-fix later to maint).
+
+ * "git clone --origin X" leaked piece of memory that held value read
+ from the clone.defaultRemoteName configuration variable, which has
+ been plugged.
+ (merge 6dfadc8981 jc/clone-remote-name-leak-fix later to maint).
+
+ * Get rid of a bogus and over-eager coccinelle rule.
+ (merge 08bdd3a185 jc/cocci-xstrdup-or-null-fix later to maint).
+
+ * The path taken by "git multi-pack-index" command from the end user
+ was compared with path internally prepared by the tool withut first
+ normalizing, which lead to duplicated paths not being noticed,
+ which has been corrected.
+ (merge 11f9e8de3d ds/midx-normalize-pathname-before-comparison later to maint).
+
+ * Correct choices of C compilers used in various CI jobs.
+ (merge 3506cae04f ab/cc-package-fixes later to maint).
+
+ * Various cleanups to "git p4".
+ (merge 4ff0108d9e jh/p4-various-fixups later to maint).
+
+ * The progress meter of "git blame" was showing incorrect numbers
+ when processing only parts of the file.
+ (merge e5f5d7d42e ea/progress-partial-blame later to maint).
+
+ * "git rebase --keep-base <upstream> <branch-to-rebase>" computed the
+ commit to rebase onto incorrectly, which has been corrected.
+ (merge 9e5ebe9668 ah/rebase-keep-base-fix later to maint).
+
+ * Fix a leak of FILE * in an error codepath.
+ (merge c0befa0c03 kt/commit-graph-plug-fp-leak-on-error later to maint).
+
+ * Avoid problems from interaction between malloc_check and address
+ sanitizer.
+ (merge 067109a5e7 pw/test-malloc-with-sanitize-address later to maint).
+
+ * The commit summary shown after making a commit is matched to what
+ is given in "git status" not to use the break-rewrite heuristics.
+ (merge 84792322ed rs/commit-summary-wo-break-rewrite later to maint).
+
+ * Update a few end-user facing messages around eol conversion.
+ (merge c970d30c2c ah/convert-warning-message later to maint).
+
+ * Trace2 documentation updates.
+ (merge a6c80c313c js/trace2-doc-fixes later to maint).
+
+ * Build procedure fixup.
+ (merge 1fbfd96f50 mg/detect-compiler-in-c-locale later to maint).
+
+ * "git pull" without "--recurse-submodules=<arg>" made
+ submodule.recurse take precedence over fetch.recurseSubmodules by
+ mistake, which has been corrected.
+ (merge 5819417365 gc/pull-recurse-submodules later to maint).
+
+ * "git bisect" was too silent before it is ready to start computing
+ the actual bisection, which has been corrected.
+ (merge f11046e6de cd/bisect-messages-from-pre-flight-states later to maint).
+
+ * macOS CI jobs have been occasionally flaky due to tentative version
+ skew between perforce and the homebrew packager. Instead of
+ failing the whole CI job, just let it skip the p4 tests when this
+ happens.
+ (merge f15e00b463 cb/ci-make-p4-optional later to maint).
+
+ * A bit of test framework fixes with a few fixes to issues found by
+ valgrind.
+ (merge 7c898554d7 ab/valgrind-fixes later to maint).
+
+ * "git archive --add-file=<path>" picked up the raw permission bits
+ from the path and propagated to zip output in some cases, without
+ normalization, which has been corrected (tar output did not have
+ this issue).
+ (merge 6a61661967 jc/archive-add-file-normalize-mode later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge e6b2582da3 cm/reftable-0-length-memset later to maint).
+ (merge 0b75e5bf22 ab/misc-cleanup later to maint).
+ (merge 52e1ab8a76 ea/rebase-code-simplify later to maint).
+ (merge 756d15923b sg/safe-directory-tests-and-docs later to maint).
+ (merge d097a23bfa ds/do-not-call-bug-on-bad-refs later to maint).
+ (merge c36c27e75c rs/t7812-pcre2-ws-bug-test later to maint).
+ (merge 1da312742d gf/unused-includes later to maint).
+ (merge 465b30a92d pb/submodule-recurse-mode-enum later to maint).
+ (merge 82b28c4ed8 km/t3501-use-test-helpers later to maint).
+ (merge 72315e431b sa/t1011-use-helpers later to maint).
+ (merge 95b3002201 cg/vscode-with-gdb later to maint).
+ (merge fbe5f6b804 tk/p4-utf8-bom later to maint).
+ (merge 17f273ffba tk/p4-with-explicity-sync later to maint).
+ (merge 944db25c60 kf/p4-multiple-remotes later to maint).
+ (merge b014cee8de jc/update-ozlabs-url later to maint).
- `gitk-git/` comes from Paul Mackerras's gitk project:
- git://ozlabs.org/~paulus/gitk
+ git://git.ozlabs.org/~paulus/gitk
+
+ Those who are interested in improve gitk can volunteer to help Paul
+ in maintaining it cf. <YntxL/fTplFm8lr6@cleo>.
- `po/` comes from the localization coordinator, Jiang Xin:
value of `false` avoids using `--auto-merge` altogether, and is the
default value.
+mergetool.vimdiff.layout::
+ The vimdiff backend uses this variable to control how its split
+ windows look like. Applies even if you are using Neovim (`nvim`) or
+ gVim (`gvim`) as the merge tool. See BACKEND SPECIFIC HINTS section
+ifndef::git-mergetool[]
+ in linkgit:git-mergetool[1].
+endif::[]
+ for details.
+
mergetool.hideResolved::
During a merge Git will automatically resolve as many conflicts as
possible and write the 'MERGED' file containing conflict markers around
`safe.directory` entry with an empty value.
+
This config setting is only respected when specified in a system or global
-config, not when it is specified in a repository config or via the command
-line option `-c safe.directory=<path>`.
+config, not when it is specified in a repository config, via the command
+line option `-c safe.directory=<path>`, or in environment variables.
+
The value of this setting is interpolated, i.e. `~/<path>` expands to a
path relative to the home directory and `%(prefix)/<path>` expands to a
CONFIGURATION
-------------
+:git-mergetool: 1
include::config/mergetool.txt[]
TEMPORARY FILES
causes `git mergetool` to automatically remove the backup as files
are successfully merged.
+BACKEND SPECIFIC HINTS
+----------------------
+
+vimdiff
+~~~~~~~
+include::mergetools/vimdiff.txt[]
+
GIT
---
Part of the linkgit:git[1] suite
Git expects paths encoded as UTF-8. Use this config to tell git-p4
what encoding Perforce had used for the paths. This encoding is used
to transcode the paths to UTF-8. As an example, Perforce on Windows
- often uses "cp1252" to encode path names.
+ often uses "cp1252" to encode path names. If this option is passed
+ into a p4 clone request, it is persisted in the resulting new git
+ repo.
+
+git-p4.metadataDecodingStrategy::
+ Perforce keeps the encoding of a changelist descriptions and user
+ full names as stored by the client on a given OS. The p4v client
+ uses the OS-local encoding, and so different users can end up storing
+ different changelist descriptions or user full names in different
+ encodings, in the same depot.
+ Git tolerates inconsistent/incorrect encodings in commit messages
+ and author names, but expects them to be specified in utf-8.
+ git-p4 can use three different decoding strategies in handling the
+ encoding uncertainty in Perforce: 'passthrough' simply passes the
+ original bytes through from Perforce to git, creating usable but
+ incorrectly-encoded data when the Perforce data is encoded as
+ anything other than utf-8. 'strict' expects the Perforce data to be
+ encoded as utf-8, and fails to import when this is not true.
+ 'fallback' attempts to interpret the data as utf-8, and otherwise
+ falls back to using a secondary encoding - by default the common
+ windows encoding 'cp-1252' - with upper-range bytes escaped if
+ decoding with the fallback encoding also fails.
+ Under python2 the default strategy is 'passthrough' for historical
+ reasons, and under python3 the default is 'fallback'.
+ When 'strict' is selected and decoding fails, the error message will
+ propose changing this config parameter as a workaround. If this
+ option is passed into a p4 clone request, it is persisted into the
+ resulting new git repo.
+
+git-p4.metadataFallbackEncoding::
+ Specify the fallback encoding to use when decoding Perforce author
+ names and changelists descriptions using the 'fallback' strategy
+ (see git-p4.metadataDecodingStrategy). The fallback encoding will
+ only be used when decoding as utf-8 fails. This option defaults to
+ cp1252, a common windows encoding. If this option is passed into a
+ p4 clone request, it is persisted into the resulting new git repo.
git-p4.largeFileSystem::
Specify the system that is used for large (binary) files. Please note
--keep-base::
Set the starting point at which to create the new commits to the
- merge base of <upstream> <branch>. Running
+ merge base of <upstream> and <branch>. Running
'git rebase --keep-base <upstream> <branch>' is equivalent to
- running 'git rebase --onto <upstream>... <upstream>'.
+ running
+ 'git rebase --onto <upstream>...<branch> <upstream> <branch>'.
+
This option is useful in the case where one is developing a feature on
top of an upstream branch. While the feature is being worked on, the
SYNOPSIS
--------
[verse]
-'git' [--version] [--help] [-C <path>] [-c <name>=<value>]
+'git' [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
OPTIONS
-------
+-v::
--version::
Prints the Git suite version that the 'git' program came from.
+
the same options as the linkgit:git-version[1] command. If `--help` is
also given, it takes precedence over `--version`.
+-h::
--help::
Prints the synopsis and a list of the most commonly used
commands. If the option `--all` or `-a` is given then all
--- /dev/null
+Description
+^^^^^^^^^^^
+
+When specifying `--tool=vimdiff` in `git mergetool` Git will open Vim with a 4
+windows layout distributed in the following way:
+....
+------------------------------------------
+| | | |
+| LOCAL | BASE | REMOTE |
+| | | |
+------------------------------------------
+| |
+| MERGED |
+| |
+------------------------------------------
+....
+`LOCAL`, `BASE` and `REMOTE` are read-only buffers showing the contents of the
+conflicting file in specific commits ("commit you are merging into", "common
+ancestor commit" and "commit you are merging from" respectively)
+
+`MERGED` is a writable buffer where you have to resolve the conflicts (using the
+other read-only buffers as a reference). Once you are done, save and exit Vim as
+usual (`:wq`) or, if you want to abort, exit using `:cq`.
+
+Layout configuration
+^^^^^^^^^^^^^^^^^^^^
+
+You can change the windows layout used by Vim by setting configuration variable
+`mergetool.vimdiff.layout` which accepts a string where the following separators
+have special meaning:
+
+ - `+` is used to "open a new tab"
+ - `,` is used to "open a new vertical split"
+ - `/` is used to "open a new horizontal split"
+ - `@` is used to indicate which is the file containing the final version after
+ solving the conflicts. If not present, `MERGED` will be used by default.
+
+The precedence of the operators is this one (you can use parentheses to change
+it):
+
+ `@` > `+` > `/` > `,`
+
+Let's see some examples to understand how it works:
+
+* `layout = "(LOCAL,BASE,REMOTE)/MERGED"`
++
+--
+This is exactly the same as the default layout we have already seen.
+
+Note that `/` has precedence over `,` and thus the parenthesis are not
+needed in this case. The next layout definition is equivalent:
+
+ layout = "LOCAL,BASE,REMOTE / MERGED"
+--
+* `layout = "LOCAL,MERGED,REMOTE"`
++
+--
+If, for some reason, we are not interested in the `BASE` buffer.
+....
+------------------------------------------
+| | | |
+| | | |
+| LOCAL | MERGED | REMOTE |
+| | | |
+| | | |
+------------------------------------------
+....
+--
+* `layout = "MERGED"`
++
+--
+Only the `MERGED` buffer will be shown. Note, however, that all the other
+ones are still loaded in vim, and you can access them with the "buffers"
+command.
+....
+------------------------------------------
+| |
+| |
+| MERGED |
+| |
+| |
+------------------------------------------
+....
+--
+* `layout = "@LOCAL,REMOTE"`
++
+--
+When `MERGED` is not present in the layout, you must "mark" one of the
+buffers with an asterisk. That will become the buffer you need to edit and
+save after resolving the conflicts.
+....
+------------------------------------------
+| | |
+| | |
+| | |
+| LOCAL | REMOTE |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+--
+* `layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE"`
++
+--
+Three tabs will open: the first one is a copy of the default layout, while
+the other two only show the differences between (`BASE` and `LOCAL`) and
+(`BASE` and `REMOTE`) respectively.
+....
+------------------------------------------
+| <TAB #1> | TAB #2 | TAB #3 | |
+------------------------------------------
+| | | |
+| LOCAL | BASE | REMOTE |
+| | | |
+------------------------------------------
+| |
+| MERGED |
+| |
+------------------------------------------
+....
+....
+------------------------------------------
+| TAB #1 | <TAB #2> | TAB #3 | |
+------------------------------------------
+| | |
+| | |
+| | |
+| BASE | LOCAL |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+....
+------------------------------------------
+| TAB #1 | TAB #2 | <TAB #3> | |
+------------------------------------------
+| | |
+| | |
+| | |
+| BASE | REMOTE |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+--
+* `layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL/BASE/REMOTE),MERGED"`
++
+--
+Same as the previous example, but adds a fourth tab with the same
+information as the first tab, with a different layout.
+....
+---------------------------------------------
+| TAB #1 | TAB #2 | TAB #3 | <TAB #4> |
+---------------------------------------------
+| LOCAL | |
+|---------------------| |
+| BASE | MERGED |
+|---------------------| |
+| REMOTE | |
+---------------------------------------------
+....
+Note how in the third tab definition we need to use parenthesis to make `,`
+have precedence over `/`.
+--
+
+Variants
+^^^^^^^^
+
+Instead of `--tool=vimdiff`, you can also use one of these other variants:
+
+ * `--tool=gvimdiff`, to open gVim instead of Vim.
+
+ * `--tool=nvimdiff`, to open Neovim instead of Vim.
+
+When using these variants, in order to specify a custom layout you will have to
+set configuration variables `mergetool.gvimdiff.layout` and
+`mergetool.nvimdiff.layout` instead of `mergetool.vimdiff.layout`
+
+In addition, for backwards compatibility with previous Git versions, you can
+also append `1`, `2` or `3` to either `vimdiff` or any of the variants (ex:
+`vimdiff3`, `nvimdiff1`, etc...) to use a predefined layout.
+In other words, using `--tool=[g,n,]vimdiffx` is the same as using
+`--tool=[g,n,]vimdiff` and setting configuration variable
+`mergetool.[g,n,]vimdiff.layout` to...
+
+ * `x=1`: `"@LOCAL, REMOTE"`
+ * `x=2`: `"LOCAL, MERGED, REMOTE"`
+ * `x=3`: `"MERGED"`
+
+Example: using `--tool=gvimdiff2` will open `gvim` with three columns (LOCAL,
+MERGED and REMOTE).
--after=<date>::
Show commits more recent than a specific date.
+--since-as-filter=<date>::
+ Show all commits more recent than a specific date. This visits
+ all commits in the range, rather than stopping at the first commit which
+ is older than a specific date.
+
--until=<date>::
--before=<date>::
Show commits older than a specific date.
explicitly enabled by enabling one or more Trace2 Targets.
The Trace2 API is intended to replace the existing (Trace1)
-printf-style tracing provided by the existing `GIT_TRACE` and
+`printf()`-style tracing provided by the existing `GIT_TRACE` and
`GIT_TRACE_PERFORMANCE` facilities. During initial implementation,
Trace2 and Trace1 may operate in parallel.
Trace2 is controlled using `trace2.*` config values in the system and
global config files and `GIT_TRACE2*` environment variables. Trace2 does
-not read from repo local or worktree config files or respect `-c`
-command line config settings.
+not read from repo local or worktree config files, nor does it respect
+`-c` command line config settings.
== Trace2 Targets
=== The Normal Format Target
-The normal format target is a tradition printf format and similar
-to GIT_TRACE format. This format is enabled with the `GIT_TRACE2`
+The normal format target is a traditional `printf()` format and similar
+to the `GIT_TRACE` format. This format is enabled with the `GIT_TRACE2`
environment variable or the `trace2.normalTarget` system or global
config setting.
=== The Performance Format Target
The performance format target (PERF) is a column-based format to
-replace GIT_TRACE_PERFORMANCE and is suitable for development and
-testing, possibly to complement tools like gprof. This format is
+replace `GIT_TRACE_PERFORMANCE` and is suitable for development and
+testing, possibly to complement tools like `gprof`. This format is
enabled with the `GIT_TRACE2_PERF` environment variable or the
`trace2.perfTarget` system or global config setting.
------------
$ cat ~/log.event
-{"event":"version","sid":"sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.620713Z","file":"common-main.c","line":38,"evt":"3","exe":"2.20.1.155.g426c96fcdb"}
+{"event":"version","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.620713Z","file":"common-main.c","line":38,"evt":"3","exe":"2.20.1.155.g426c96fcdb"}
{"event":"start","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621027Z","file":"common-main.c","line":39,"t_abs":0.001173,"argv":["git","version"]}
{"event":"cmd_name","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621122Z","file":"git.c","line":432,"name":"version","hierarchy":"version"}
{"event":"exit","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621236Z","file":"git.c","line":662,"t_abs":0.001227,"code":0}
take a `va_list` argument.
Some functions have a `_printf_fl()` suffix to indicate that they also
-take a varargs argument.
+take a `printf()` style format with a variable number of arguments.
-There are CPP wrapper macros and ifdefs to hide most of these details.
+There are CPP wrapper macros and `#ifdef`s to hide most of these details.
See `trace2.h` for more details. The following discussion will only
describe the simplified forms.
is the event name.
`<event-message>`::
- is a free-form printf message intended for human consumption.
+ is a free-form `printf()` message intended for human consumption.
+
Note that this may contain embedded LF or CRLF characters that are
not escaped, so the event may spill across multiple lines.
indicate a broad category, such as "index" or "status".
`<perf-event-message>`::
- is a free-form printf message intended for human consumption.
+ is a free-form `printf()` message intended for human consumption.
------------
15:33:33.532712 wt-status.c:2310 | d0 | main | region_enter | r1 | 0.126064 | | status | label:print
------------
`"cmd_mode"`::
- This event, when present, describes the command variant This
+ This event, when present, describes the command variant. This
event may be emitted more than once.
+
------------
`"child_exit"`::
This event is generated after the current process has returned
- from the waitpid() and collected the exit information from the
+ from the `waitpid()` and collected the exit information from the
child.
+
------------
+
Note that the `t_rel` field contains the observed run time in seconds
for the child process (starting before the fork/exec/spawn and
-stopping after the waitpid() and includes OS process creation overhead).
+stopping after the `waitpid()` and includes OS process creation overhead).
So this time will be slightly larger than the atexit time reported by
the child process itself.
+
This event is generated after the child is started in the background
and given a little time to boot up and start working. If the child
-startups normally and while the parent is still waiting, the "ready"
+starts up normally while the parent is still waiting, the "ready"
field will have the value "ready".
If the child is too slow to start and the parent times out, the field
will have the value "timeout".
Regions::
- Regions can be use to time an interesting section of code.
+ Regions can be used to time an interesting section of code.
+
----------------
void wt_status_collect(struct wt_status *s)
Thread messages added to a thread-proc.
+
-For example, the multithreaded preload-index code can be
+For example, the multi-threaded preload-index code can be
instrumented with a region around the thread pool and then
-per-thread start and exit events within the threadproc.
+per-thread start and exit events within the thread-proc.
+
----------------
static void *preload_thread(void *_data)
There are a few issues to resolve before we can completely
switch to Trace2.
-* Updating existing tests that assume GIT_TRACE format messages.
+* Updating existing tests that assume `GIT_TRACE` format messages.
-* How to best handle custom GIT_TRACE_<key> messages?
+* How to best handle custom `GIT_TRACE_<key>` messages?
-** The GIT_TRACE_<key> mechanism allows each <key> to write to a
+** The `GIT_TRACE_<key>` mechanism allows each <key> to write to a
different file (in addition to just stderr).
** Do we want to maintain that ability or simply write to the existing
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.36.1
+DEF_VER=v2.36.GIT
LF='
'
SPARSE_FLAGS ?= -std=gnu99
SP_EXTRA_FLAGS = -Wno-universal-initializer
-# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak target
+# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
SANITIZE_LEAK =
+SANITIZE_ADDRESS =
# For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
# usually result in less CPU usage at the cost of higher peak memory.
endif
ifneq ($(filter address,$(SANITIZERS)),)
NO_REGEX = NeededForASAN
+SANITIZE_ADDRESS = YesCompiledWithIt
endif
endif
@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
@echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
+ @echo SANITIZE_ADDRESS=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_ADDRESS)))'\' >>$@+
@echo X=\'$(X)\' >>$@+
ifdef FSMONITOR_DAEMON_BACKEND
@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
$(RM) coverage-untested-functions
$(RM) -r cover_db/
$(RM) -r cover_db_html/
+ $(RM) coverage-test.made
coverage-clean: coverage-clean-results
$(RM) $(addsuffix *.gcno,$(object_dirs))
coverage-test: coverage-clean-results coverage-compile
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
DEFAULT_TEST_TARGET=test -j1 test
+ touch coverage-test.made
+
+coverage-test.made:
+ $(MAKE) coverage-test
coverage-prove: coverage-clean-results coverage-compile
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
DEFAULT_TEST_TARGET=prove GIT_PROVE_OPTS="$(GIT_PROVE_OPTS) -j1" \
-j1 test
-coverage-report:
+coverage-report: coverage-test.made
$(QUIET_GCOV)for dir in $(object_dirs); do \
$(GCOV) $(GCOVFLAGS) --object-directory=$$dir $$dir*.c || exit; \
done
-Documentation/RelNotes/2.36.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.37.0.txt
\ No newline at end of file
};
struct alloc_state {
- int count; /* total number of nodes allocated */
int nr; /* number of nodes left in current allocation */
void *p; /* first free node in current allocation */
s->slabs[s->slab_nr++] = s->p;
}
s->nr--;
- s->count++;
ret = s->p;
s->p = (char *)s->p + node_size;
memset(ret, 0, node_size);
init_commit_node(c);
return c;
}
-
-static void report(const char *name, unsigned int count, size_t size)
-{
- fprintf(stderr, "%10s: %8u (%"PRIuMAX" kB)\n",
- name, count, (uintmax_t) size);
-}
-
-#define REPORT(name, type) \
- report(#name, r->parsed_objects->name##_state->count, \
- r->parsed_objects->name##_state->count * sizeof(type) >> 10)
-
-void alloc_report(struct repository *r)
-{
- REPORT(blob, struct blob);
- REPORT(tree, struct tree);
- REPORT(commit, struct commit);
- REPORT(tag, struct tag);
- REPORT(object, union any_object);
-}
void *alloc_commit_node(struct repository *r);
void *alloc_tag_node(struct repository *r);
void *alloc_object_node(struct repository *r);
-void alloc_report(struct repository *r);
struct alloc_state *allocate_alloc_state(void);
void clear_alloc_state(struct alloc_state *s);
{
struct string_list_item *item;
- if (name == NULL)
+ if (!name)
return NULL;
item = string_list_lookup(&state->fn_table, name);
- if (item != NULL)
+ if (item)
return (struct patch *)item->util;
return NULL;
* This should cover the cases for normal diffs,
* file creations and copies
*/
- if (patch->new_name != NULL) {
+ if (patch->new_name) {
item = string_list_insert(&state->fn_table, patch->new_name);
item->util = patch;
}
else
err = write_entry(args, &fake_oid, path_in_archive.buf,
path_in_archive.len,
- info->stat.st_mode,
+ canon_mode(info->stat.st_mode),
content.buf, content.len);
if (err)
break;
}
tree = parse_tree_indirect(&oid);
- if (tree == NULL)
+ if (!tree)
die(_("not a tree object: %s"), oid_to_hex(&oid));
if (prefix) {
BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11
};
+/*
+ * Stores how many good/bad commits we have stored for a bisect. nr_bad can
+ * only be 0 or 1.
+ */
+struct bisect_state {
+ unsigned int nr_good;
+ unsigned int nr_bad;
+};
+
enum bisect_error bisect_next_all(struct repository *r, const char *prefix);
int estimate_bisect_steps(int all);
if (p1->s_lno <= p2->s_lno) {
do {
tail = &p1->next;
- if ((p1 = *tail) == NULL) {
+ if (!(p1 = *tail)) {
*tail = p2;
return list1;
}
*tail = p2;
do {
tail = &p2->next;
- if ((p2 = *tail) == NULL) {
+ if (!(p2 = *tail)) {
*tail = p1;
return list1;
}
*tail = p1;
do {
tail = &p1->next;
- if ((p1 = *tail) == NULL) {
+ if (!(p1 = *tail)) {
*tail = p2;
return list1;
}
break;
}
- if ((commit = lookup_commit_reference(r, &oid)) == NULL)
+ if (!(commit = lookup_commit_reference(r, &oid)))
die(_("not a valid branch point: '%s'"), start_name);
if (out_real_ref) {
*out_real_ref = real_ref;
* be created in every submodule.
*/
for (i = 0; i < submodule_entry_list.entry_nr; i++) {
- if (submodule_entry_list.entries[i].repo == NULL) {
+ if (!submodule_entry_list.entries[i].repo) {
int code = die_message(
_("submodule '%s': unable to find submodule"),
submodule_entry_list.entries[i].submodule->name);
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
-#include "lockfile.h"
#include "apply.h"
static const char * const apply_usage[] = {
return 0;
}
-static int mark_good(const char *refname, const struct object_id *oid,
- int flag, void *cb_data)
+static int inc_nr(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- int *m_good = (int *)cb_data;
- *m_good = 0;
- return 1;
+ unsigned int *nr = (unsigned int *)cb_data;
+ (*nr)++;
+ return 0;
}
static const char need_bad_and_good_revision_warning[] =
vocab_good, vocab_bad, vocab_good, vocab_bad);
}
-static int bisect_next_check(const struct bisect_terms *terms,
- const char *current_term)
+static void bisect_status(struct bisect_state *state,
+ const struct bisect_terms *terms)
{
- int missing_good = 1, missing_bad = 1;
char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
char *good_glob = xstrfmt("%s-*", terms->term_good);
if (ref_exists(bad_ref))
- missing_bad = 0;
+ state->nr_bad = 1;
- for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
- (void *) &missing_good);
+ for_each_glob_ref_in(inc_nr, good_glob, "refs/bisect/",
+ (void *) &state->nr_good);
free(good_glob);
free(bad_ref);
+}
- return decide_next(terms, current_term, missing_good, missing_bad);
+__attribute__((format (printf, 1, 2)))
+static void bisect_log_printf(const char *fmt, ...)
+{
+ struct strbuf buf = STRBUF_INIT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ strbuf_vaddf(&buf, fmt, ap);
+ va_end(ap);
+
+ printf("%s", buf.buf);
+ append_to_file(git_path_bisect_log(), "# %s", buf.buf);
+
+ strbuf_release(&buf);
+}
+
+static void bisect_print_status(const struct bisect_terms *terms)
+{
+ struct bisect_state state = { 0 };
+
+ bisect_status(&state, terms);
+
+ /* If we had both, we'd already be started, and shouldn't get here. */
+ if (state.nr_good && state.nr_bad)
+ return;
+
+ if (!state.nr_good && !state.nr_bad)
+ bisect_log_printf(_("status: waiting for both good and bad commits\n"));
+ else if (state.nr_good)
+ bisect_log_printf(Q_("status: waiting for bad commit, %d good commit known\n",
+ "status: waiting for bad commit, %d good commits known\n",
+ state.nr_good), state.nr_good);
+ else
+ bisect_log_printf(_("status: waiting for good commit(s), bad commit known\n"));
+}
+
+static int bisect_next_check(const struct bisect_terms *terms,
+ const char *current_term)
+{
+ struct bisect_state state = { 0 };
+ bisect_status(&state, terms);
+ return decide_next(terms, current_term, !state.nr_good, !state.nr_bad);
}
static int get_terms(struct bisect_terms *terms)
if (get_terms(terms))
return error(_("no terms defined"));
- if (option == NULL) {
+ if (!option) {
printf(_("Your current terms are %s for the old state\n"
"and %s for the new state.\n"),
terms->term_good, terms->term_bad);
static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)
{
- if (bisect_next_check(terms, NULL))
+ if (bisect_next_check(terms, NULL)) {
+ bisect_print_status(terms);
return BISECT_OK;
+ }
return bisect_next(terms, prefix);
}
unsigned int range_i;
long anchor;
const int hexsz = the_hash_algo->hexsz;
+ long num_lines = 0;
setup_default_color_by_age();
git_config(git_blame_config, &output_option);
for (range_i = ranges.nr; range_i > 0; --range_i) {
const struct range *r = &ranges.ranges[range_i - 1];
ent = blame_entry_prepend(ent, r->start, r->end, o);
+ num_lines += (r->end - r->start);
}
+ if (!num_lines)
+ num_lines = sb.num_lines;
o->suspects = ent;
prio_queue_put(&sb.commits, o->commit);
sb.found_guilty_entry = &found_guilty_entry;
sb.found_guilty_entry_data = π
if (show_progress)
- pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines);
+ pi.progress = start_delayed_progress(_("Blaming lines"), num_lines);
assign_blame(&sb, opt);
if (ret)
return ret;
o.ancestor = old_branch_info->name;
- if (old_branch_info->name == NULL) {
+ if (!old_branch_info->name) {
strbuf_add_unique_abbrev(&old_commit_shortname,
&old_branch_info->commit->object.oid,
DEFAULT_ABBREV);
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
*/
- if (option_origin != NULL)
+ if (option_origin) {
+ free(remote_name);
remote_name = xstrdup(option_origin);
+ }
- if (remote_name == NULL)
+ if (!remote_name)
remote_name = xstrdup("origin");
if (!valid_remote_name(remote_name))
}
s->fp = fopen_for_writing(git_path_commit_editmsg());
- if (s->fp == NULL)
+ if (!s->fp)
die_errno(_("could not open '%s'"), git_path_commit_editmsg());
/* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
struct commit *current_head = NULL;
struct commit_extra_header *extra = NULL;
struct strbuf err = STRBUF_INIT;
+ int ret = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
running hooks, writing the trees, and interacting with the user. */
if (!prepare_to_commit(index_file, prefix,
current_head, &s, &author_ident)) {
+ ret = 1;
rollback_index_files();
- return 1;
+ goto cleanup;
}
/* Determine parents */
rollback_index_files();
die(_("failed to write commit object"));
}
- strbuf_release(&author_ident);
free_commit_extra_headers(extra);
if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
apply_autostash(git_path_merge_autostash(the_repository));
+cleanup:
+ UNLEAK(author_ident);
UNLEAK(err);
UNLEAK(sb);
- return 0;
+ return ret;
}
othercount++;
continue;
}
- if (map == NULL)
+ if (!map)
map = bitmap_new();
bitmap_set(map, i);
}
fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
/* be gentle to concurrent "gc" on remote hosts */
(strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
- if (fp != NULL)
+ if (fp)
fclose(fp);
if (should_exit) {
if (fd >= 0)
goto error;
}
file = fopen_or_warn(filename, "w");
- if (file == NULL)
+ if (!file)
goto error;
unit = "# This file was created and is maintained by Git.\n"
filename = xdg_config_home_systemd("git-maintenance@.service");
file = fopen_or_warn(filename, "w");
- if (file == NULL)
+ if (!file)
goto error;
unit = "# This file was created and is maintained by Git.\n"
free(objects);
strbuf_release(&index_name_buf);
strbuf_release(&rev_index_name_buf);
- if (pack_name == NULL)
+ if (!pack_name)
free((void *) curr_pack);
- if (index_name == NULL)
+ if (!index_name)
free((void *) curr_index);
- if (rev_index_name == NULL)
+ if (!rev_index_name)
free((void *) curr_rev_index);
/*
init_log_defaults();
git_config(git_log_config, NULL);
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
+
memset(&match_all, 0, sizeof(match_all));
repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);
if (!quiet)
printf("%s\n", filename.buf + outdir_offset);
- if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) {
+ if (!(rev->diffopt.file = fopen(filename.buf, "w"))) {
error_errno(_("cannot open patch file %s"), filename.buf);
strbuf_release(&filename);
return -1;
}
transport = transport_get(remote, NULL);
- if (uploadpack != NULL)
+ if (uploadpack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
if (server_options.nr)
transport->server_options = &server_options;
for (sub = subs; *sub; ++sub) {
free(name);
name = xstrfmt("%s/%s", path, *sub);
- if ((dir = opendir(name)) == NULL) {
+ if (!(dir = opendir(name))) {
if (errno == ENOENT)
continue;
error_errno("cannot opendir %s", name);
};
static struct opts_multi_pack_index {
- const char *object_dir;
+ char *object_dir;
const char *preferred_pack;
const char *refs_snapshot;
unsigned long batch_size;
int stdin_packs;
} opts;
+
+static int parse_object_dir(const struct option *opt, const char *arg,
+ int unset)
+{
+ free(opts.object_dir);
+ if (unset)
+ opts.object_dir = xstrdup(get_object_directory());
+ else
+ opts.object_dir = real_pathdup(arg, 1);
+ return 0;
+}
+
static struct option common_opts[] = {
- OPT_FILENAME(0, "object-dir", &opts.object_dir,
- N_("object directory containing set of packfile and pack-index pairs")),
+ OPT_CALLBACK(0, "object-dir", &opts.object_dir,
+ N_("directory"),
+ N_("object directory containing set of packfile and pack-index pairs"),
+ parse_object_dir),
OPT_END(),
};
int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
+ int res;
struct option *builtin_multi_pack_index_options = common_opts;
git_config(git_default_config, NULL);
+ if (the_repository &&
+ the_repository->objects &&
+ the_repository->objects->odb)
+ opts.object_dir = xstrdup(the_repository->objects->odb->path);
+
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- if (!opts.object_dir)
- opts.object_dir = get_object_directory();
-
if (!argc)
goto usage;
if (!strcmp(argv[0], "repack"))
- return cmd_multi_pack_index_repack(argc, argv);
+ res = cmd_multi_pack_index_repack(argc, argv);
else if (!strcmp(argv[0], "write"))
- return cmd_multi_pack_index_write(argc, argv);
+ res = cmd_multi_pack_index_write(argc, argv);
else if (!strcmp(argv[0], "verify"))
- return cmd_multi_pack_index_verify(argc, argv);
+ res = cmd_multi_pack_index_verify(argc, argv);
else if (!strcmp(argv[0], "expire"))
- return cmd_multi_pack_index_expire(argc, argv);
+ res = cmd_multi_pack_index_expire(argc, argv);
+ else {
+ error(_("unrecognized subcommand: %s"), argv[0]);
+ goto usage;
+ }
+
+ free(opts.object_dir);
+ return res;
- error(_("unrecognized subcommand: %s"), argv[0]);
usage:
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
oidread(&new_item->oid, oid);
new_item->next = NULL;
- if (after != NULL) {
+ if (after) {
new_item->next = after->next;
after->next = new_item;
if (after == list->back)
if (cmp > 0) /* not in list, since sorted */
return prev;
if (!cmp) { /* found */
- if (prev == NULL) {
+ if (!prev) {
if (hint != NULL && hint != list->front) {
/* we don't know the previous element */
hint = NULL;
struct pack_list *ret;
const struct pack_list *pl;
- if (A == NULL)
+ if (!A)
return NULL;
pl = B;
struct pack_list *subset;
size_t ret = 0;
- if (pl == NULL)
+ if (!pl)
return 0;
while ((subset = pl->next)) {
while (*(argv + i) != NULL)
add_pack_file(*(argv + i++));
- if (local_packs == NULL)
+ if (!local_packs)
die("Zero packs found!");
load_all_objects();
static int opt_verbosity;
static char *opt_progress;
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
/* Options passed to git-merge or git-rebase */
static enum rebase_type opt_rebase = -1;
N_("force progress reporting"),
PARSE_OPT_NOARG),
OPT_CALLBACK_F(0, "recurse-submodules",
- &recurse_submodules, N_("on-demand"),
+ &recurse_submodules_cli, N_("on-demand"),
N_("control for recursive fetching of submodules"),
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
strvec_push(&args, opt_tags);
if (opt_prune)
strvec_push(&args, opt_prune);
- if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
- switch (recurse_submodules) {
+ if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
+ switch (recurse_submodules_cli) {
case RECURSE_SUBMODULES_ON:
strvec_push(&args, "--recurse-submodules=on");
break;
argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+ if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
+ recurse_submodules = recurse_submodules_cli;
+
if (cleanup_arg)
/*
* this only checks the validity of cleanup_arg; we don't need
} else {
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/interactive", merge_dir());
- if(file_exists(buf.buf)) {
- options.type = REBASE_MERGE;
+ options.type = REBASE_MERGE;
+ if (file_exists(buf.buf))
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
- } else
- options.type = REBASE_MERGE;
}
options.state_dir = merge_dir();
}
options.upstream_arg = "--root";
}
- /* Make sure the branch to rebase onto is valid. */
- if (keep_base) {
- strbuf_reset(&buf);
- strbuf_addstr(&buf, options.upstream_name);
- strbuf_addstr(&buf, "...");
- options.onto_name = xstrdup(buf.buf);
- } else if (!options.onto_name)
- options.onto_name = options.upstream_name;
- if (strstr(options.onto_name, "...")) {
- if (get_oid_mb(options.onto_name, &merge_base) < 0) {
- if (keep_base)
- die(_("'%s': need exactly one merge base with branch"),
- options.upstream_name);
- else
- die(_("'%s': need exactly one merge base"),
- options.onto_name);
- }
- options.onto = lookup_commit_or_die(&merge_base,
- options.onto_name);
- } else {
- options.onto =
- lookup_commit_reference_by_name(options.onto_name);
- if (!options.onto)
- die(_("Does not point to a valid commit '%s'"),
- options.onto_name);
- }
-
/*
* If the branch to rebase is given, that is the branch we will rebase
* branch_name -- branch/commit being rebased, or
} else
BUG("unexpected number of arguments left to parse");
+ /* Make sure the branch to rebase onto is valid. */
+ if (keep_base) {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, options.upstream_name);
+ strbuf_addstr(&buf, "...");
+ strbuf_addstr(&buf, branch_name);
+ options.onto_name = xstrdup(buf.buf);
+ } else if (!options.onto_name)
+ options.onto_name = options.upstream_name;
+ if (strstr(options.onto_name, "...")) {
+ if (get_oid_mb(options.onto_name, &merge_base) < 0) {
+ if (keep_base)
+ die(_("'%s': need exactly one merge base with branch"),
+ options.upstream_name);
+ else
+ die(_("'%s': need exactly one merge base"),
+ options.onto_name);
+ }
+ options.onto = lookup_commit_or_die(&merge_base,
+ options.onto_name);
+ } else {
+ options.onto =
+ lookup_commit_reference_by_name(options.onto_name);
+ if (!options.onto)
+ die(_("Does not point to a valid commit '%s'"),
+ options.onto_name);
+ }
+
if (options.fork_point > 0) {
struct commit *head =
lookup_commit_reference(the_repository,
}
dst_name = strip_namespace(dst_name);
- if ((item = string_list_lookup(list, dst_name)) == NULL)
+ if (!(item = string_list_lookup(list, dst_name)))
return;
cmd->skip_update = 1;
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_DIE_ON_ERR_PACKET);
- if ((commands = read_head_info(&reader, &shallow)) != NULL) {
+ if ((commands = read_head_info(&reader, &shallow))) {
const char *unpack_status = NULL;
struct string_list push_options = STRING_LIST_INIT_DUP;
{
struct show_data data;
- if (pattern == NULL)
+ if (!pattern)
pattern = "*";
data.pattern = pattern;
/* name(s) */
s = strpbrk(sb.buf, flag_chars);
- if (s == NULL)
+ if (!s)
s = help;
if (s - sb.buf == 1) /* short option only */
prefix = setup_git_directory();
git_config(git_default_config, NULL);
did_repo_setup = 1;
+
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
}
if (!strcmp(arg, "--")) {
format_subject(&subject, oneline, " ");
buffer = strbuf_detach(&subject, NULL);
- if (item->util == NULL)
+ if (!item->util)
item->util = xcalloc(1, sizeof(struct string_list));
string_list_append(item->util, buffer);
}
"--all/--remotes/--independent/--merge-base");
}
+ if (with_current_branch && reflog)
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--reflog", "--current");
+
/* If nothing is specified, show all branches by default */
if (ac <= topics && all_heads + all_remotes == 0)
all_heads = 1;
#include "cache-tree.h"
#include "unpack-trees.h"
#include "merge-recursive.h"
+#include "merge-ort-wrappers.h"
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
{
- int ret;
+ int clean, ret;
int has_index = index;
struct merge_options o;
struct object_id c_tree;
struct object_id index_tree;
- struct commit *result;
- const struct object_id *bases[1];
+ struct tree *head, *merge, *merge_base;
+ struct lock_file lock = LOCK_INIT;
read_cache_preload(NULL);
if (refresh_and_write_cache(REFRESH_QUIET, 0, 0))
o.branch1 = "Updated upstream";
o.branch2 = "Stashed changes";
+ o.ancestor = "Stash base";
if (oideq(&info->b_tree, &c_tree))
o.branch1 = "Version stash was based on";
if (o.verbosity >= 3)
printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
- bases[0] = &info->b_tree;
+ head = lookup_tree(o.repo, &c_tree);
+ merge = lookup_tree(o.repo, &info->w_tree);
+ merge_base = lookup_tree(o.repo, &info->b_tree);
+
+ repo_hold_locked_index(o.repo, &lock, LOCK_DIE_ON_ERROR);
+ clean = merge_ort_nonrecursive(&o, head, merge, merge_base);
+
+ /*
+ * If 'clean' >= 0, reverse the value for 'ret' so 'ret' is 0 when the
+ * merge was clean, and nonzero if the merge was unclean or encountered
+ * an error.
+ */
+ ret = clean >= 0 ? !clean : clean;
+
+ if (ret < 0)
+ rollback_lock_file(&lock);
+ else if (write_locked_index(o.repo->index, &lock,
+ COMMIT_LOCK | SKIP_IF_UNCHANGED))
+ ret = error(_("could not write index"));
- ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
- &result);
if (ret) {
rerere(0);
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
index_file = get_index_file();
strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
(uintmax_t)pid);
strbuf_addstr(sb, "object of unknown type");
break;
case OBJ_COMMIT:
- if ((buf = read_object_file(oid, &type, &size)) != NULL) {
+ if ((buf = read_object_file(oid, &type, &size))) {
subject_len = find_commit_subject(buf, &subject_start);
strbuf_insert(sb, sb->len, subject_start, subject_len);
} else {
}
free(buf);
- if ((c = lookup_commit_reference(the_repository, oid)) != NULL)
+ if ((c = lookup_commit_reference(the_repository, oid)))
strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
break;
case OBJ_TREE:
int is_inside_work_tree(void);
const char *get_git_dir(void);
const char *get_git_common_dir(void);
-char *get_object_directory(void);
+const char *get_object_directory(void);
char *get_index_file(void);
char *get_graft_file(struct repository *r);
void set_git_dir(const char *path, int make_realpath);
. ${0%/*}/lib.sh
-P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION
+P4WHENCE=https://cdist2.perforce.com/perforce/r$LINUX_P4_VERSION
LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev
tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl
test -z "$BREW_INSTALL_PACKAGES" ||
brew install $BREW_INSTALL_PACKAGES
brew link --force gettext
- brew install --cask --no-quarantine perforce || {
- # Update the definitions and try again
- cask_repo="$(brew --repository)"/Library/Taps/homebrew/homebrew-cask &&
- git -C "$cask_repo" pull --no-stat --ff-only &&
- brew install --cask --no-quarantine perforce
- } ||
- brew install homebrew/cask/perforce
+ mkdir -p $HOME/bin
+ (
+ cd $HOME/bin
+ wget -q "https://cdist2.perforce.com/perforce/r21.2/bin.macosx1015x86_64/helix-core-server.tgz" &&
+ tar -xf helix-core-server.tgz &&
+ sudo xattr -d com.apple.quarantine p4 p4d 2>/dev/null || true
+ )
+ PATH="$PATH:${HOME}/bin"
+ export PATH
if test -n "$CC_PACKAGE"
then
;;
esac
-if type p4d >/dev/null && type p4 >/dev/null
+if type p4d >/dev/null 2>&1 && type p4 >/dev/null 2>&1
then
echo "$(tput setaf 6)Perforce Server Version$(tput sgr0)"
p4d -V | grep Rev.
echo "$(tput setaf 6)Perforce Client Version$(tput sgr0)"
p4 -V | grep Rev.
+else
+ echo >&2 "WARNING: perforce wasn't installed, see above for clues why"
fi
-if type git-lfs >/dev/null
+if type git-lfs >/dev/null 2>&1
then
echo "$(tput setaf 6)Git-LFS Version$(tput sgr0)"
git-lfs version
+else
+ echo >&2 "WARNING: git-lfs wasn't installed, see above for clues why"
fi
struct lline *baseend, *newend = NULL;
int i, j, origbaselen = *lenbase;
- if (newline == NULL)
+ if (!newline)
return base;
- if (base == NULL) {
+ if (!base) {
*lenbase = lennew;
return newline;
}
stat_res = stat(chain_name, &st);
free(chain_name);
- if (!fp ||
- stat_res ||
- st.st_size <= the_hash_algo->hexsz)
+ if (!fp)
return NULL;
+ if (stat_res ||
+ st.st_size <= the_hash_algo->hexsz) {
+ fclose(fp);
+ return NULL;
+ }
count = st.st_size / (the_hash_algo->hexsz + 1);
CALLOC_ARRAY(oids, count);
struct stat st;
struct utimbuf updated_time;
- stat(ctx->commit_graph_filenames_before[i], &st);
+ if (stat(ctx->commit_graph_filenames_before[i], &st) < 0)
+ continue;
updated_time.actime = st.st_atime;
updated_time.modtime = now;
strbuf_setlen(&path, dirnamelen);
strbuf_addstr(&path, de->d_name);
- stat(path.buf, &st);
+ if (stat(path.buf, &st) < 0)
+ continue;
if (st.st_mtime > expire_time)
continue;
odb_parents = odb_commit->parents;
while (graph_parents) {
- if (odb_parents == NULL) {
+ if (!odb_parents) {
graph_report(_("commit-graph parent list for commit %s is too long"),
oid_to_hex(&cur_oid));
break;
odb_parents = odb_parents->next;
}
- if (odb_parents != NULL)
+ if (odb_parents)
graph_report(_("commit-graph parent list for commit %s terminates early"),
oid_to_hex(&cur_oid));
data->cfar_paths_to_watch,
kFSEventStreamEventIdSinceNow,
0.001, flags);
- if (data->stream == NULL)
+ if (!data->stream)
goto failed;
/*
int mkstemp(char *template)
{
char *filename = mktemp(template);
- if (filename == NULL)
+ if (!filename)
return -1;
return open(filename, O_RDWR | O_CREAT, 0600);
}
static const struct timeval zero;
static int atexit_done;
- if (out != NULL)
+ if (out)
return errno = EINVAL,
error("setitimer param 3 != NULL not implemented");
if (!is_timeval_eq(&in->it_interval, &zero) &&
if (sig != SIGALRM)
return errno = EINVAL,
error("sigaction only implemented for SIGALRM");
- if (out != NULL)
+ if (out)
return errno = EINVAL,
error("sigaction: param 3 != NULL not implemented");
size_t len = strlen(dir);
if (len && dir[len-1] == '/') {
- if ((tmp_dir = strdup(dir)) == NULL)
+ if (!(tmp_dir = strdup(dir)))
return -1;
tmp_dir[len-1] = '\0';
}
}
start = malloc(length);
- if (start == NULL) {
+ if (!start) {
errno = ENOMEM;
return MAP_FAILED;
}
goto out_free;
}
/* if nothing to unset, error out */
- if (value == NULL) {
+ if (!value) {
ret = CONFIG_NOTHING_SET;
goto out_free;
}
int i, new_line = 0;
struct config_options opts;
- if (value_pattern == NULL)
+ if (!value_pattern)
store.value_pattern = NULL;
else if (value_pattern == CONFIG_REGEX_NONE)
store.value_pattern = CONFIG_REGEX_NONE;
}
/* write the pair (value == NULL means unset) */
- if (value != NULL) {
+ if (value) {
if (!store.section_seen) {
if (write_section(fd, key, &store) < 0)
goto write_err_out;
offset = section_name_match(&buf[i], old_name);
if (offset > 0) {
ret++;
- if (new_name == NULL) {
+ if (!new_name) {
remove = 1;
continue;
}
GIT_CONF_SUBST([HAVE_BSD_SYSCTL])
## Other checks.
-# Define USE_PIC if you need the main git objects to be built with -fPIC
-# in order to build and link perl/Git.so. x86-64 seems to need this.
-#
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
# Enable it on Windows. By default, symrefs are still used.
#
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+@@
+expression e;
+statement s;
+@@
+if (
+(
+!e
+|
+- e == NULL
++ !e
+)
+ )
+ {...}
+else s
+
+@@
+expression e;
+statement s;
+@@
+if (
+(
+e
+|
+- e != NULL
++ e
+)
+ )
+ {...}
+else s
+ repo_read_object_file(the_repository,
E, F, G)
-@@
-expression E;
-@@
-- has_sha1_file(
-+ repo_has_sha1_file(the_repository,
- E)
-
-@@
-expression E;
-expression F;
-@@
-- has_sha1_file_with_flags(
-+ repo_has_sha1_file_with_flags(the_repository,
- E)
-
@@
expression E;
@@
[Windows](https://code.visualstudio.com/docs/setup/windows),
[macOS](https://code.visualstudio.com/docs/setup/mac) and
[Linux](https://code.visualstudio.com/docs/setup/linux). Among other languages,
-it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools).
+it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools) with
+[debugging support](https://code.visualstudio.com/docs/editor/debugging)
+
+To get help about "how to personalize your settings" read:
+[How to set up your settings](https://code.visualstudio.com/docs/getstarted/settings)
To start developing Git with VS Code, simply run the Unix shell script called
`init.sh` in this directory, which creates the configuration files in
"stopAtEntry": false,
"cwd": "\${workspaceFolder}",
"environment": [],
- "externalConsole": true,
"MIMode": "gdb",
"miDebuggerPath": "$GDBPATH",
"setupCommands": [
if (conv_flags & CONV_EOL_RNDTRP_DIE)
die(_("CRLF would be replaced by LF in %s"), path);
else if (conv_flags & CONV_EOL_RNDTRP_WARN)
- warning(_("CRLF will be replaced by LF in %s.\n"
- "The file will have its original line"
- " endings in your working directory"), path);
+ warning(_("in the working copy of '%s', CRLF will be"
+ " replaced by LF the next time Git touches"
+ " it"), path);
} else if (old_stats->lonelf && !new_stats->lonelf ) {
/*
* CRLFs would be added by checkout
if (conv_flags & CONV_EOL_RNDTRP_DIE)
die(_("LF would be replaced by CRLF in %s"), path);
else if (conv_flags & CONV_EOL_RNDTRP_WARN)
- warning(_("LF will be replaced by CRLF in %s.\n"
- "The file will have its original line"
- " endings in your working directory"), path);
+ warning(_("in the working copy of '%s', LF will be"
+ " replaced by CRLF the next time Git touches"
+ " it"), path);
}
}
FILE *fp;
fp = fdopen(fd, "r");
- if (fp == NULL) {
+ if (!fp) {
logerror("fdopen of error channel failed");
close(fd);
return;
#
# FreeBSD clang version 3.4.1 (tags/RELEASE...)
get_version_line() {
- $CC -v 2>&1 | grep ' version '
+ LANG=C LC_ALL=C $CC -v 2>&1 | grep ' version '
}
get_family() {
int mode)
{
struct strbuf buf = STRBUF_INIT;
- struct strbuf tempfile = STRBUF_INIT;
char *path_dup = xstrdup(path);
const char *base = basename(path_dup);
struct checkout_metadata meta;
init_checkout_metadata(&meta, NULL, NULL, oid);
- /* Generate "XXXXXX_basename.ext" */
- strbuf_addstr(&tempfile, "XXXXXX_");
- strbuf_addstr(&tempfile, base);
-
- temp->tempfile = mks_tempfile_ts(tempfile.buf, strlen(base) + 1);
+ temp->tempfile = mks_tempfile_dt("git-blob-XXXXXX", base);
if (!temp->tempfile)
die_errno("unable to create temp-file");
if (convert_to_working_tree(istate, path,
oid_to_hex_r(temp->hex, oid);
xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
strbuf_release(&buf);
- strbuf_release(&tempfile);
free(path_dup);
}
strbuf_addch(&uc->ident, 0);
}
-static void new_untracked_cache(struct index_state *istate)
+static unsigned new_untracked_cache_flags(struct index_state *istate)
+{
+ struct repository *repo = istate->repo;
+ char *val;
+
+ /*
+ * This logic is coordinated with the setting of these flags in
+ * wt-status.c#wt_status_collect_untracked(), and the evaluation
+ * of the config setting in commit.c#git_status_config()
+ */
+ if (!repo_config_get_string(repo, "status.showuntrackedfiles", &val) &&
+ !strcmp(val, "all"))
+ return 0;
+
+ /*
+ * The default, if "all" is not set, is "normal" - leading us here.
+ * If the value is "none" then it really doesn't matter.
+ */
+ return DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+}
+
+static void new_untracked_cache(struct index_state *istate, int flags)
{
struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
strbuf_init(&uc->ident, 100);
uc->exclude_per_dir = ".gitignore";
- /* should be the same flags used by git-status */
- uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+ uc->dir_flags = flags >= 0 ? flags : new_untracked_cache_flags(istate);
set_untracked_ident(uc);
istate->untracked = uc;
istate->cache_changed |= UNTRACKED_CHANGED;
void add_untracked_cache(struct index_state *istate)
{
if (!istate->untracked) {
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
} else {
if (!ident_in_untracked(istate->untracked)) {
free_untracked_cache(istate->untracked);
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
}
}
}
if (base_len || (pathspec && pathspec->nr))
return NULL;
- /* Different set of flags may produce different results */
- if (dir->flags != dir->untracked->dir_flags ||
- /*
- * See treat_directory(), case index_nonexistent. Without
- * this flag, we may need to also cache .git file content
- * for the resolve_gitlink_ref() call, which we don't.
- */
- !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
- /* We don't support collecting ignore files */
- (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
- DIR_COLLECT_IGNORED)))
+ /* We don't support collecting ignore files */
+ if (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+ DIR_COLLECT_IGNORED))
return NULL;
/*
return NULL;
}
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as requested in this run, we're going to need to either discard the
+ * existing structure (and potentially later recreate), or bypass the
+ * untracked cache mechanism for this run.
+ */
+ if (dir->flags != dir->untracked->dir_flags) {
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as configured, then we need to reset / create a new "untracked"
+ * structure to match the new config.
+ *
+ * Keeping the saved and used untracked cache consistent with the
+ * configuration provides an opportunity for frequent users of
+ * "git status -uall" to leverage the untracked cache by aligning their
+ * configuration - setting "status.showuntrackedfiles" to "all" or
+ * "normal" as appropriate.
+ *
+ * Previously using -uall (or setting "status.showuntrackedfiles" to
+ * "all") was incompatible with untracked cache and *consistently*
+ * caused surprisingly bad performance (with fscache and fsmonitor
+ * enabled) on Windows.
+ *
+ * IMPROVEMENT OPPORTUNITY: If we reworked the untracked cache storage
+ * to not be as bound up with the desired output in a given run,
+ * and instead iterated through and stored enough information to
+ * correctly serve both "modes", then users could get peak performance
+ * with or without '-uall' regardless of their
+ * "status.showuntrackedfiles" config.
+ */
+ if (dir->untracked->dir_flags != new_untracked_cache_flags(istate)) {
+ free_untracked_cache(istate->untracked);
+ new_untracked_cache(istate, dir->flags);
+ dir->untracked = istate->untracked;
+ }
+ else {
+ /*
+ * Current untracked cache data is consistent with config, but not
+ * usable in this request/run; just bypass untracked cache.
+ */
+ return NULL;
+ }
+ }
+
if (!dir->untracked->root) {
/* Untracked cache existed but is not initialized; fix that */
FLEX_ALLOC_STR(dir->untracked->root, name, "");
* Skip scheme.
*/
start = strstr(repo, "://");
- if (start == NULL)
+ if (!start)
start = repo;
else
start += 3;
return the_repository->worktree;
}
-char *get_object_directory(void)
+const char *get_object_directory(void)
{
if (!the_repository->objects->odb)
BUG("git environment hasn't been setup");
return 0;
}
-void bitmap_reset(struct bitmap *bitmap)
-{
- memset(bitmap->words, 0x0, bitmap->word_alloc * sizeof(eword_t));
-}
-
void bitmap_free(struct bitmap *bitmap)
{
- if (bitmap == NULL)
+ if (!bitmap)
return;
free(bitmap->words);
void ewah_pool_free(struct ewah_bitmap *self)
{
- if (self == NULL)
+ if (!self)
return;
if (bitmap_pool_size == BITMAP_POOL_MAX ||
void bitmap_set(struct bitmap *self, size_t pos);
void bitmap_unset(struct bitmap *self, size_t pos);
int bitmap_get(struct bitmap *self, size_t pos);
-void bitmap_reset(struct bitmap *self);
void bitmap_free(struct bitmap *self);
int bitmap_equals(struct bitmap *self, struct bitmap *other);
int bitmap_is_subset(struct bitmap *self, struct bitmap *other);
static int process_section_header(struct packet_reader *reader,
const char *section, int peek)
{
- int ret;
-
- if (packet_reader_peek(reader) != PACKET_READ_NORMAL)
- die(_("error reading section header '%s'"), section);
+ int ret = 0;
- ret = !strcmp(reader->line, section);
+ if (packet_reader_peek(reader) == PACKET_READ_NORMAL &&
+ !strcmp(reader->line, section))
+ ret = 1;
if (!peek) {
- if (!ret)
- die(_("expected '%s', received '%s'"),
- section, reader->line);
+ if (!ret) {
+ if (reader->line)
+ die(_("expected '%s', received '%s'"),
+ section, reader->line);
+ else
+ die(_("expected '%s'"), section);
+ }
packet_reader_read(reader);
}
preamble=
fi
shown_any=yes
- printf "%s%s\n" "$per_line_prefix" "$toolname"
+ printf "%s%-15s %s\n" "$per_line_prefix" "$toolname" $(diff_mode && diff_cmd_help "$toolname" || merge_cmd_help "$toolname")
fi
done
return 1
}
+ diff_cmd_help () {
+ return 0
+ }
+
merge_cmd () {
return 1
}
+ merge_cmd_help () {
+ return 0
+ }
+
hide_resolved_enabled () {
return 0
}
# 2007 Trolltech ASA
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
-# pylint: disable=invalid-name,missing-docstring,too-many-arguments,broad-except
-# pylint: disable=no-self-use,wrong-import-position,consider-iterating-dictionary
-# pylint: disable=wrong-import-order,unused-import,too-few-public-methods
-# pylint: disable=too-many-lines,ungrouped-imports,fixme,too-many-locals
-# pylint: disable=line-too-long,bad-whitespace,superfluous-parens
-# pylint: disable=too-many-statements,too-many-instance-attributes
-# pylint: disable=too-many-branches,too-many-nested-blocks
+# pylint: disable=bad-whitespace
+# pylint: disable=broad-except
+# pylint: disable=consider-iterating-dictionary
+# pylint: disable=disable
+# pylint: disable=fixme
+# pylint: disable=invalid-name
+# pylint: disable=line-too-long
+# pylint: disable=missing-docstring
+# pylint: disable=no-self-use
+# pylint: disable=superfluous-parens
+# pylint: disable=too-few-public-methods
+# pylint: disable=too-many-arguments
+# pylint: disable=too-many-branches
+# pylint: disable=too-many-instance-attributes
+# pylint: disable=too-many-lines
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-nested-blocks
+# pylint: disable=too-many-statements
+# pylint: disable=ungrouped-imports
+# pylint: disable=unused-import
+# pylint: disable=wrong-import-order
+# pylint: disable=wrong-import-position
#
+
+import struct
import sys
if sys.version_info.major < 3 and sys.version_info.minor < 7:
sys.stderr.write("git-p4: requires Python 2.7 or later.\n")
sys.exit(1)
-import os
-import optparse
+
+import ctypes
+import errno
import functools
+import glob
import marshal
-import subprocess
-import tempfile
-import time
+import optparse
+import os
import platform
import re
import shutil
import stat
+import subprocess
+import tempfile
+import time
import zipfile
import zlib
-import ctypes
-import errno
-import glob
# On python2.7 where raw_input() and input() are both availble,
# we want raw_input's semantics, but aliased to input for python3
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
# The block size is reduced automatically if required
-defaultBlockSize = 1<<20
+defaultBlockSize = 1 << 20
+
+defaultMetadataDecodingStrategy = 'passthrough' if sys.version_info.major == 2 else 'fallback'
+defaultFallbackMetadataEncoding = 'cp1252'
p4_access_checked = False
re_ko_keywords = re.compile(br'\$(Id|Header)(:[^$\n]+)?\$')
re_k_keywords = re.compile(br'\$(Id|Header|Author|Date|DateTime|Change|File|Revision)(:[^$\n]+)?\$')
+
def format_size_human_readable(num):
- """ Returns a number of units (typically bytes) formatted as a human-readable
- string.
- """
+ """Returns a number of units (typically bytes) formatted as a
+ human-readable string.
+ """
if num < 1024:
return '{:d} B'.format(num)
for unit in ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
return "{:3.1f} {}B".format(num, unit)
return "{:.1f} YiB".format(num)
+
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
- This consolidates building and returning a p4 command line into one
- location. It means that hooking into the environment, or other configuration
- can be done more easily.
- """
+ This consolidates building and returning a p4 command line into one
+ location. It means that hooking into the environment, or other
+ configuration can be done more easily.
+ """
real_cmd = ["p4"]
user = gitConfig("git-p4.user")
if len(user) > 0:
- real_cmd += ["-u",user]
+ real_cmd += ["-u", user]
password = gitConfig("git-p4.password")
if len(password) > 0:
return real_cmd
+
def git_dir(path):
- """ Return TRUE if the given path is a git directory (/path/to/dir/.git).
- This won't automatically add ".git" to a directory.
- """
+ """Return TRUE if the given path is a git directory (/path/to/dir/.git).
+ This won't automatically add ".git" to a directory.
+ """
d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip()
if not d or len(d) == 0:
return None
else:
return d
+
def chdir(path, is_client_path=False):
- """Do chdir to the given path, and set the PWD environment
- variable for use by P4. It does not look at getcwd() output.
- Since we're not using the shell, it is necessary to set the
- PWD environment variable explicitly.
-
- Normally, expand the path to force it to be absolute. This
- addresses the use of relative path names inside P4 settings,
- e.g. P4CONFIG=.p4config. P4 does not simply open the filename
- as given; it looks for .p4config using PWD.
-
- If is_client_path, the path was handed to us directly by p4,
- and may be a symbolic link. Do not call os.getcwd() in this
- case, because it will cause p4 to think that PWD is not inside
- the client path.
+ """Do chdir to the given path, and set the PWD environment variable for use
+ by P4. It does not look at getcwd() output. Since we're not using the
+ shell, it is necessary to set the PWD environment variable explicitly.
+
+ Normally, expand the path to force it to be absolute. This addresses
+ the use of relative path names inside P4 settings, e.g.
+ P4CONFIG=.p4config. P4 does not simply open the filename as given; it
+ looks for .p4config using PWD.
+
+ If is_client_path, the path was handed to us directly by p4, and may be
+ a symbolic link. Do not call os.getcwd() in this case, because it will
+ cause p4 to think that PWD is not inside the client path.
"""
os.chdir(path)
path = os.getcwd()
os.environ['PWD'] = path
+
def calcDiskFree():
"""Return free space in bytes on the disk of the given dirname."""
if platform.system() == 'Windows':
st = os.statvfs(os.getcwd())
return st.f_bavail * st.f_frsize
+
def die(msg):
- """ Terminate execution. Make sure that any running child processes have been wait()ed for before
- calling this.
- """
+ """Terminate execution. Make sure that any running child processes have
+ been wait()ed for before calling this.
+ """
if verbose:
raise Exception(msg)
else:
sys.stderr.write(msg + "\n")
sys.exit(1)
+
def prompt(prompt_text):
- """ Prompt the user to choose one of the choices
+ """Prompt the user to choose one of the choices.
- Choices are identified in the prompt_text by square brackets around
- a single letter option.
- """
+ Choices are identified in the prompt_text by square brackets around a
+ single letter option.
+ """
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
while True:
sys.stderr.flush()
sys.stdout.write(prompt_text)
sys.stdout.flush()
- response=sys.stdin.readline().strip().lower()
+ response = sys.stdin.readline().strip().lower()
if not response:
continue
response = response[0]
if response in choices:
return response
+
# We need different encoding/decoding strategies for text data being passed
# around in pipes depending on python version
if bytes is not str:
# For python3, always encode and decode as appropriate
def decode_text_stream(s):
return s.decode() if isinstance(s, bytes) else s
+
def encode_text_stream(s):
return s.encode() if isinstance(s, str) else s
else:
# For python2.7, pass read strings as-is, but also allow writing unicode
def decode_text_stream(s):
return s
+
def encode_text_stream(s):
return s.encode('utf_8') if isinstance(s, unicode) else s
+
+class MetadataDecodingException(Exception):
+ def __init__(self, input_string):
+ self.input_string = input_string
+
+ def __str__(self):
+ return """Decoding perforce metadata failed!
+The failing string was:
+---
+{}
+---
+Consider setting the git-p4.metadataDecodingStrategy config option to
+'fallback', to allow metadata to be decoded using a fallback encoding,
+defaulting to cp1252.""".format(self.input_string)
+
+
+encoding_fallback_warning_issued = False
+encoding_escape_warning_issued = False
+def metadata_stream_to_writable_bytes(s):
+ encodingStrategy = gitConfig('git-p4.metadataDecodingStrategy') or defaultMetadataDecodingStrategy
+ fallbackEncoding = gitConfig('git-p4.metadataFallbackEncoding') or defaultFallbackMetadataEncoding
+ if not isinstance(s, bytes):
+ return s.encode('utf_8')
+ if encodingStrategy == 'passthrough':
+ return s
+ try:
+ s.decode('utf_8')
+ return s
+ except UnicodeDecodeError:
+ if encodingStrategy == 'fallback' and fallbackEncoding:
+ global encoding_fallback_warning_issued
+ global encoding_escape_warning_issued
+ try:
+ if not encoding_fallback_warning_issued:
+ print("\nCould not decode value as utf-8; using configured fallback encoding %s: %s" % (fallbackEncoding, s))
+ print("\n(this warning is only displayed once during an import)")
+ encoding_fallback_warning_issued = True
+ return s.decode(fallbackEncoding).encode('utf_8')
+ except Exception as exc:
+ if not encoding_escape_warning_issued:
+ print("\nCould not decode value with configured fallback encoding %s; escaping bytes over 127: %s" % (fallbackEncoding, s))
+ print("\n(this warning is only displayed once during an import)")
+ encoding_escape_warning_issued = True
+ escaped_bytes = b''
+ # bytes and strings work very differently in python2 vs python3...
+ if str is bytes:
+ for byte in s:
+ byte_number = struct.unpack('>B', byte)[0]
+ if byte_number > 127:
+ escaped_bytes += b'%'
+ escaped_bytes += hex(byte_number)[2:].upper()
+ else:
+ escaped_bytes += byte
+ else:
+ for byte_number in s:
+ if byte_number > 127:
+ escaped_bytes += b'%'
+ escaped_bytes += hex(byte_number).upper().encode()[2:]
+ else:
+ escaped_bytes += bytes([byte_number])
+ return escaped_bytes
+
+ raise MetadataDecodingException(s)
+
+
def decode_path(path):
- """Decode a given string (bytes or otherwise) using configured path encoding options
- """
+ """Decode a given string (bytes or otherwise) using configured path
+ encoding options.
+ """
+
encoding = gitConfig('git-p4.pathEncoding') or 'utf_8'
if bytes is not str:
return path.decode(encoding, errors='replace') if isinstance(path, bytes) else path
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
return path
+
def run_git_hook(cmd, param=[]):
"""Execute a hook if the hook exists."""
args = ['git', 'hook', 'run', '--ignore-missing', cmd]
args.append(p)
return subprocess.call(args) == 0
+
def write_pipe(c, stdin, *k, **kw):
if verbose:
sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
return val
+
def p4_write_pipe(c, stdin, *k, **kw):
real_cmd = p4_build_cmd(c)
if bytes is not str and isinstance(stdin, str):
stdin = encode_text_stream(stdin)
return write_pipe(real_cmd, stdin, *k, **kw)
+
def read_pipe_full(c, *k, **kw):
- """ Read output from command. Returns a tuple
- of the return status, stdout text and stderr
- text.
- """
+ """Read output from command. Returns a tuple of the return status, stdout
+ text and stderr text.
+ """
if verbose:
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
p = subprocess.Popen(
c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw)
- (out, err) = p.communicate()
+ out, err = p.communicate()
return (p.returncode, out, decode_text_stream(err))
+
def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
- """ Read output from command. Returns the output text on
- success. On failure, terminates execution, unless
- ignore_error is True, when it returns an empty string.
+ """Read output from command. Returns the output text on success. On
+ failure, terminates execution, unless ignore_error is True, when it
+ returns an empty string.
- If raw is True, do not attempt to decode output text.
- """
- (retcode, out, err) = read_pipe_full(c, *k, **kw)
+ If raw is True, do not attempt to decode output text.
+ """
+ retcode, out, err = read_pipe_full(c, *k, **kw)
if retcode != 0:
if ignore_error:
out = ""
out = decode_text_stream(out)
return out
+
def read_pipe_text(c, *k, **kw):
- """ Read output from a command with trailing whitespace stripped.
- On error, returns None.
- """
- (retcode, out, err) = read_pipe_full(c, *k, **kw)
+ """Read output from a command with trailing whitespace stripped. On error,
+ returns None.
+ """
+ retcode, out, err = read_pipe_full(c, *k, **kw)
if retcode != 0:
return None
else:
return decode_text_stream(out).rstrip()
+
def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
real_cmd = p4_build_cmd(c)
return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
+
def read_pipe_lines(c, raw=False, *k, **kw):
if verbose:
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
die('Command failed: {}'.format(' '.join(c)))
return lines
+
def p4_read_pipe_lines(c, *k, **kw):
- """Specifically invoke p4 on the command supplied. """
+ """Specifically invoke p4 on the command supplied."""
real_cmd = p4_build_cmd(c)
return read_pipe_lines(real_cmd, *k, **kw)
+
def p4_has_command(cmd):
- """Ask p4 for help on this command. If it returns an error, the
- command does not exist in this version of p4."""
+ """Ask p4 for help on this command. If it returns an error, the command
+ does not exist in this version of p4.
+ """
real_cmd = p4_build_cmd(["help", cmd])
p = subprocess.Popen(real_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p.communicate()
return p.returncode == 0
+
def p4_has_move_command():
- """See if the move command exists, that it supports -k, and that
- it has not been administratively disabled. The arguments
- must be correct, but the filenames do not have to exist. Use
- ones with wildcards so even if they exist, it will fail."""
+ """See if the move command exists, that it supports -k, and that it has not
+ been administratively disabled. The arguments must be correct, but the
+ filenames do not have to exist. Use ones with wildcards so even if they
+ exist, it will fail.
+ """
if not p4_has_command("move"):
return False
cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (out, err) = p.communicate()
+ out, err = p.communicate()
err = decode_text_stream(err)
# return code will be 1 in either case
if err.find("Invalid option") >= 0:
# assume it failed because @... was invalid changelist
return True
+
def system(cmd, ignore_error=False, *k, **kw):
if verbose:
sys.stderr.write("executing {}\n".format(
return retcode
+
def p4_system(cmd, *k, **kw):
- """Specifically invoke p4 as the system command. """
+ """Specifically invoke p4 as the system command."""
real_cmd = p4_build_cmd(cmd)
retcode = subprocess.call(real_cmd, *k, **kw)
if retcode:
raise subprocess.CalledProcessError(retcode, real_cmd)
+
def die_bad_access(s):
die("failure accessing depot: {0}".format(s.rstrip()))
+
def p4_check_access(min_expiration=1):
- """ Check if we can access Perforce - account still logged in
- """
+ """Check if we can access Perforce - account still logged in."""
+
results = p4CmdList(["login", "-s"])
if len(results) == 0:
else:
die_bad_access("unknown error code {0}".format(code))
+
_p4_version_string = None
+
+
def p4_version_string():
- """Read the version string, showing just the last line, which
- hopefully is the interesting version bit.
+ """Read the version string, showing just the last line, which hopefully is
+ the interesting version bit.
$ p4 -V
Perforce - The Fast Software Configuration Management System.
Copyright 1995-2011 Perforce Software. All rights reserved.
Rev. P4/NTX86/2011.1/393975 (2011/12/16).
- """
+ """
global _p4_version_string
if not _p4_version_string:
a = p4_read_pipe_lines(["-V"])
_p4_version_string = a[-1].rstrip()
return _p4_version_string
+
def p4_integrate(src, dest):
p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
+
def p4_sync(f, *options):
p4_system(["sync"] + list(options) + [wildcard_encode(f)])
+
def p4_add(f):
- # forcibly add file names with wildcards
+ """Forcibly add file names with wildcards."""
if wildcard_present(f):
p4_system(["add", "-f", f])
else:
p4_system(["add", f])
+
def p4_delete(f):
p4_system(["delete", wildcard_encode(f)])
+
def p4_edit(f, *options):
p4_system(["edit"] + list(options) + [wildcard_encode(f)])
+
def p4_revert(f):
p4_system(["revert", wildcard_encode(f)])
+
def p4_reopen(type, f):
p4_system(["reopen", "-t", type, wildcard_encode(f)])
+
def p4_reopen_in_change(changelist, files):
cmd = ["reopen", "-c", str(changelist)] + files
p4_system(cmd)
+
def p4_move(src, dest):
p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
+
def p4_last_change():
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
return int(results[0]['change'])
+
def p4_describe(change, shelved=False):
- """Make sure it returns a valid result by checking for
- the presence of field "time". Return a dict of the
- results."""
+ """Make sure it returns a valid result by checking for the presence of
+ field "time".
+
+ Return a dict of the results.
+ """
cmd = ["describe", "-s"]
if shelved:
return d
-#
-# Canonicalize the p4 type and return a tuple of the
-# base type, plus any modifiers. See "p4 help filetypes"
-# for a list and explanation.
-#
+
def split_p4_type(p4type):
+ """Canonicalize the p4 type and return a tuple of the base type, plus any
+ modifiers. See "p4 help filetypes" for a list and explanation.
+ """
p4_filetypes_historical = {
"ctempobj": "binary+Sw",
mods = s[1]
return (base, mods)
-#
-# return the raw p4 type of a file (text, text+ko, etc)
-#
+
def p4_type(f):
+ """Return the raw p4 type of a file (text, text+ko, etc)."""
+
results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
return results[0]['headType']
-#
-# Given a type base and modifier, return a regexp matching
-# the keywords that can be expanded in the file
-#
+
def p4_keywords_regexp_for_type(base, type_mods):
+ """Given a type base and modifier, return a regexp matching the keywords
+ that can be expanded in the file.
+ """
+
if base in ("text", "unicode", "binary"):
if "ko" in type_mods:
return re_ko_keywords
else:
return None
-#
-# Given a file, return a regexp matching the possible
-# RCS keywords that will be expanded, or None for files
-# with kw expansion turned off.
-#
+
def p4_keywords_regexp_for_file(file):
+ """Given a file, return a regexp matching the possible RCS keywords that
+ will be expanded, or None for files with kw expansion turned off.
+ """
+
if not os.path.exists(file):
return None
else:
- (type_base, type_mods) = split_p4_type(p4_type(file))
+ type_base, type_mods = split_p4_type(p4_type(file))
return p4_keywords_regexp_for_type(type_base, type_mods)
+
def setP4ExecBit(file, mode):
- # Reopens an already open file and changes the execute bit to match
- # the execute bit setting in the passed in mode.
+ """Reopens an already open file and changes the execute bit to match the
+ execute bit setting in the passed in mode.
+ """
p4Type = "+x"
p4_reopen(p4Type, file)
+
def getP4OpenedType(file):
- # Returns the perforce file type for the given file.
+ """Returns the perforce file type for the given file."""
result = p4_read_pipe(["opened", wildcard_encode(file)])
match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
else:
die("Could not determine file type for %s (result: '%s')" % (file, result))
-# Return the set of all p4 labels
+
def getP4Labels(depotPaths):
+ """Return the set of all p4 labels."""
+
labels = set()
if not isinstance(depotPaths, list):
depotPaths = [depotPaths]
return labels
-# Return the set of all git tags
+
def getGitTags():
+ """Return the set of all git tags."""
+
gitTags = set()
for line in read_pipe_lines(["git", "tag"]):
tag = line.strip()
gitTags.add(tag)
return gitTags
+
_diff_tree_pattern = None
+
def parseDiffTreeEntry(entry):
"""Parses a single diff tree entry into its component elements.
- See git-diff-tree(1) manpage for details about the format of the diff
- output. This method returns a dictionary with the following elements:
-
- src_mode - The mode of the source file
- dst_mode - The mode of the destination file
- src_sha1 - The sha1 for the source file
- dst_sha1 - The sha1 fr the destination file
- status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
- status_score - The score for the status (applicable for 'C' and 'R'
- statuses). This is None if there is no score.
- src - The path for the source file.
- dst - The path for the destination file. This is only present for
- copy or renames. If it is not present, this is None.
-
- If the pattern is not matched, None is returned."""
+ See git-diff-tree(1) manpage for details about the format of the diff
+ output. This method returns a dictionary with the following elements:
+
+ src_mode - The mode of the source file
+ dst_mode - The mode of the destination file
+ src_sha1 - The sha1 for the source file
+ dst_sha1 - The sha1 fr the destination file
+ status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
+ status_score - The score for the status (applicable for 'C' and 'R'
+ statuses). This is None if there is no score.
+ src - The path for the source file.
+ dst - The path for the destination file. This is only present for
+ copy or renames. If it is not present, this is None.
+
+ If the pattern is not matched, None is returned.
+ """
global _diff_tree_pattern
if not _diff_tree_pattern:
}
return None
+
def isModeExec(mode):
- # Returns True if the given git mode represents an executable file,
- # otherwise False.
+ """Returns True if the given git mode represents an executable file,
+ otherwise False.
+ """
return mode[-3:] == "755"
+
class P4Exception(Exception):
- """ Base class for exceptions from the p4 client """
+ """Base class for exceptions from the p4 client."""
+
def __init__(self, exit_code):
self.p4ExitCode = exit_code
+
class P4ServerException(P4Exception):
- """ Base class for exceptions where we get some kind of marshalled up result from the server """
+ """Base class for exceptions where we get some kind of marshalled up result
+ from the server.
+ """
+
def __init__(self, exit_code, p4_result):
super(P4ServerException, self).__init__(exit_code)
self.p4_result = p4_result
self.code = p4_result[0]['code']
self.data = p4_result[0]['data']
+
class P4RequestSizeException(P4ServerException):
- """ One of the maxresults or maxscanrows errors """
+ """One of the maxresults or maxscanrows errors."""
+
def __init__(self, exit_code, p4_result, limit):
super(P4RequestSizeException, self).__init__(exit_code, p4_result)
self.limit = limit
+
class P4CommandException(P4Exception):
- """ Something went wrong calling p4 which means we have to give up """
+ """Something went wrong calling p4 which means we have to give up."""
+
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
+
def isModeExecChanged(src_mode, dst_mode):
return isModeExec(src_mode) != isModeExec(dst_mode)
+
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
errors_as_exceptions=False, *k, **kw):
if bytes is not str:
# Decode unmarshalled dict to use str keys and values, except for:
# - `data` which may contain arbitrary binary data
- # - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text
+ # - `desc` or `FullName` which may contain non-UTF8 encoded text handled below, eagerly converted to bytes
+ # - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text, handled by decode_path()
decoded_entry = {}
for key, value in entry.items():
key = key.decode()
- if isinstance(value, bytes) and not (key in ('data', 'path', 'clientFile') or key.startswith('depotFile')):
+ if isinstance(value, bytes) and not (key in ('data', 'desc', 'FullName', 'path', 'clientFile') or key.startswith('depotFile')):
value = value.decode()
decoded_entry[key] = value
# Parse out data if it's an error response
if skip_info:
if 'code' in entry and entry['code'] == 'info':
continue
+ if 'desc' in entry:
+ entry['desc'] = metadata_stream_to_writable_bytes(entry['desc'])
+ if 'FullName' in entry:
+ entry['FullName'] = metadata_stream_to_writable_bytes(entry['FullName'])
if cb is not None:
cb(entry)
else:
return result
+
def p4Cmd(cmd, *k, **kw):
list = p4CmdList(cmd, *k, **kw)
result = {}
for entry in list:
result.update(entry)
- return result;
+ return result
+
def p4Where(depotPath):
if not depotPath.endswith("/"):
if data[:space] == depotPath:
output = entry
break
- if output == None:
+ if output is None:
return ""
if output["code"] == "error":
return ""
clientPath = clientPath[:-3]
return clientPath
+
def currentGitBranch():
return read_pipe_text(["git", "symbolic-ref", "--short", "-q", "HEAD"])
+
def isValidGitDir(path):
- return git_dir(path) != None
+ return git_dir(path) is not None
+
def parseRevision(ref):
return read_pipe(["git", "rev-parse", ref]).strip()
+
def branchExists(ref):
rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
ignore_error=True)
return len(rev) > 0
+
def extractLogMessageFromGitCommit(commit):
logMessage = ""
- ## fixme: title is first line of commit, not 1st paragraph.
+ # fixme: title is first line of commit, not 1st paragraph.
foundTitle = False
for log in read_pipe_lines(["git", "cat-file", "commit", commit]):
- if not foundTitle:
- if len(log) == 1:
- foundTitle = True
- continue
+ if not foundTitle:
+ if len(log) == 1:
+ foundTitle = True
+ continue
- logMessage += log
+ logMessage += log
return logMessage
+
def extractSettingsGitLog(log):
values = {}
for line in log.split("\n"):
line = line.strip()
- m = re.search (r"^ *\[git-p4: (.*)\]$", line)
+ m = re.search(r"^ *\[git-p4: (.*)\]$", line)
if not m:
continue
- assignments = m.group(1).split (':')
+ assignments = m.group(1).split(':')
for a in assignments:
- vals = a.split ('=')
+ vals = a.split('=')
key = vals[0].strip()
- val = ('='.join (vals[1:])).strip()
- if val.endswith ('\"') and val.startswith('"'):
+ val = ('='.join(vals[1:])).strip()
+ if val.endswith('\"') and val.startswith('"'):
val = val[1:-1]
values[key] = val
values['depot-paths'] = paths.split(',')
return values
+
def gitBranchExists(branch):
proc = subprocess.Popen(["git", "rev-parse", branch],
- stderr=subprocess.PIPE, stdout=subprocess.PIPE);
- return proc.wait() == 0;
+ stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ return proc.wait() == 0
+
def gitUpdateRef(ref, newvalue):
subprocess.check_call(["git", "update-ref", ref, newvalue])
+
def gitDeleteRef(ref):
subprocess.check_call(["git", "update-ref", "-d", ref])
+
_gitConfig = {}
+
def gitConfig(key, typeSpecifier=None):
if key not in _gitConfig:
- cmd = [ "git", "config" ]
+ cmd = ["git", "config"]
if typeSpecifier:
- cmd += [ typeSpecifier ]
- cmd += [ key ]
+ cmd += [typeSpecifier]
+ cmd += [key]
s = read_pipe(cmd, ignore_error=True)
_gitConfig[key] = s.strip()
return _gitConfig[key]
+
def gitConfigBool(key):
"""Return a bool, using git config --bool. It is True only if the
variable is set to true, and False if set to false or not present
- in the config."""
+ in the config.
+ """
if key not in _gitConfig:
_gitConfig[key] = gitConfig(key, '--bool') == "true"
return _gitConfig[key]
+
def gitConfigInt(key):
if key not in _gitConfig:
- cmd = [ "git", "config", "--int", key ]
+ cmd = ["git", "config", "--int", key]
s = read_pipe(cmd, ignore_error=True)
v = s.strip()
try:
_gitConfig[key] = None
return _gitConfig[key]
+
def gitConfigList(key):
if key not in _gitConfig:
s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
_gitConfig[key] = []
return _gitConfig[key]
+def fullP4Ref(incomingRef, importIntoRemotes=True):
+ """Standardize a given provided p4 ref value to a full git ref:
+ refs/foo/bar/branch -> use it exactly
+ p4/branch -> prepend refs/remotes/ or refs/heads/
+ branch -> prepend refs/remotes/p4/ or refs/heads/p4/"""
+ if incomingRef.startswith("refs/"):
+ return incomingRef
+ if importIntoRemotes:
+ prepend = "refs/remotes/"
+ else:
+ prepend = "refs/heads/"
+ if not incomingRef.startswith("p4/"):
+ prepend += "p4/"
+ return prepend + incomingRef
+
+def shortP4Ref(incomingRef, importIntoRemotes=True):
+ """Standardize to a "short ref" if possible:
+ refs/foo/bar/branch -> ignore
+ refs/remotes/p4/branch or refs/heads/p4/branch -> shorten
+ p4/branch -> shorten"""
+ if importIntoRemotes:
+ longprefix = "refs/remotes/p4/"
+ else:
+ longprefix = "refs/heads/p4/"
+ if incomingRef.startswith(longprefix):
+ return incomingRef[len(longprefix):]
+ if incomingRef.startswith("p4/"):
+ return incomingRef[3:]
+ return incomingRef
+
def p4BranchesInGit(branchesAreInRemotes=True):
"""Find all the branches whose names start with "p4/", looking
in remotes or heads as specified by the argument. Return
a dictionary of { branch: revision } for each one found.
The branch names are the short names, without any
- "p4/" prefix."""
+ "p4/" prefix.
+ """
branches = {}
return branches
+
def branch_exists(branch):
"""Make sure that the given ref name really exists."""
- cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
+ cmd = ["git", "rev-parse", "--symbolic", "--verify", branch]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, _ = p.communicate()
out = decode_text_stream(out)
# expect exactly one line of output: the branch name
return out.rstrip() == branch
-def findUpstreamBranchPoint(head = "HEAD"):
+
+def findUpstreamBranchPoint(head="HEAD"):
branches = p4BranchesInGit()
# map from depot-path to branch name
branchByDepotPath = {}
log = extractLogMessageFromGitCommit(tip)
settings = extractSettingsGitLog(log)
if "depot-paths" in settings:
+ git_branch = "remotes/p4/" + branch
paths = ",".join(settings["depot-paths"])
- branchByDepotPath[paths] = "remotes/p4/" + branch
+ branchByDepotPath[paths] = git_branch
+ if "change" in settings:
+ paths = paths + ";" + settings["change"]
+ branchByDepotPath[paths] = git_branch
settings = None
parent = 0
settings = extractSettingsGitLog(log)
if "depot-paths" in settings:
paths = ",".join(settings["depot-paths"])
+ if "change" in settings:
+ expaths = paths + ";" + settings["change"]
+ if expaths in branchByDepotPath:
+ return [branchByDepotPath[expaths], settings]
if paths in branchByDepotPath:
return [branchByDepotPath[paths], settings]
return ["", settings]
-def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
+
+def createOrUpdateBranchesFromOrigin(localRefPrefix="refs/remotes/p4/", silent=True):
if not silent:
print("Creating/updating branch(es) in %s based on origin branch(es)"
% localRefPrefix)
originHead = line
original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
- if ('depot-paths' not in original
- or 'change' not in original):
+ if 'depot-paths' not in original or 'change' not in original:
continue
update = False
if update:
system(["git", "update-ref", remoteHead, originHead])
+
def originP4BranchesExist():
- return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
+ return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
def p4ParseNumericChangeRange(parts):
return (changeStart, changeEnd)
+
def chooseBlockSize(blockSize):
if blockSize:
return blockSize
else:
return defaultBlockSize
+
def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
assert depotPaths
parts = changeRange.split(',')
assert len(parts) == 2
try:
- (changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
+ changeStart, changeEnd = p4ParseNumericChangeRange(parts)
block_size = chooseBlockSize(requestedBlockSize)
except ValueError:
changeStart = parts[0][1:]
else:
block_size = max(2, block_size // 2)
- if verbose: print("block size error, retrying with block size {0}".format(block_size))
+ if verbose:
+ print("block size error, retrying with block size {0}".format(block_size))
continue
except P4Exception as e:
die('Error retrieving changes description ({0})'.format(e.p4ExitCode))
changes = sorted(changes)
return changes
+
def p4PathStartsWith(path, prefix):
- # This method tries to remedy a potential mixed-case issue:
- #
- # If UserA adds //depot/DirA/file1
- # and UserB adds //depot/dira/file2
- #
- # we may or may not have a problem. If you have core.ignorecase=true,
- # we treat DirA and dira as the same directory
+ """This method tries to remedy a potential mixed-case issue:
+
+ If UserA adds //depot/DirA/file1
+ and UserB adds //depot/dira/file2
+
+ we may or may not have a problem. If you have core.ignorecase=true,
+ we treat DirA and dira as the same directory.
+ """
if gitConfigBool("core.ignorecase"):
return path.lower().startswith(prefix.lower())
return path.startswith(prefix)
+
def getClientSpec():
"""Look at the p4 client spec, create a View() object that contains
- all the mappings, and return it."""
+ all the mappings, and return it.
+ """
specList = p4CmdList(["client", "-o"])
if len(specList) != 1:
client_name = entry["Client"]
# just the keys that start with "View"
- view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+ view_keys = [k for k in entry.keys() if k.startswith("View")]
# hold this new View
view = View(client_name)
return view
+
def getClientRoot():
"""Grab the client directory."""
return entry["Root"]
-#
-# P4 wildcards are not allowed in filenames. P4 complains
-# if you simply add them, but you can force it with "-f", in
-# which case it translates them into %xx encoding internally.
-#
+
def wildcard_decode(path):
+ """Decode P4 wildcards into %xx encoding
+
+ P4 wildcards are not allowed in filenames. P4 complains if you simply
+ add them, but you can force it with "-f", in which case it translates
+ them into %xx encoding internally.
+ """
+
# Search for and fix just these four characters. Do % last so
# that fixing it does not inadvertently create new %-escapes.
# Cannot have * in a filename in windows; untested as to
.replace("%25", "%")
return path
+
def wildcard_encode(path):
+ """Encode %xx coded wildcards into P4 coding."""
+
# do % first to avoid double-encoding the %s introduced here
path = path.replace("%", "%25") \
.replace("*", "%2A") \
.replace("@", "%40")
return path
+
def wildcard_present(path):
m = re.search("[*#@%]", path)
return m is not None
+
class LargeFileSystem(object):
"""Base class for large file system support."""
self.writeToGitStream = writeToGitStream
def generatePointer(self, cloneDestination, contentFile):
- """Return the content of a pointer file that is stored in Git instead of
- the actual content."""
+ """Return the content of a pointer file that is stored in Git instead
+ of the actual content.
+ """
assert False, "Method 'generatePointer' required in " + self.__class__.__name__
def pushFile(self, localLargeFile):
"""Push the actual content which is not stored in the Git repository to
- a server."""
+ a server.
+ """
assert False, "Method 'pushFile' required in " + self.__class__.__name__
def hasLargeFileExtension(self, relPath):
def processContent(self, git_mode, relPath, contents):
"""Processes the content of git fast import. This method decides if a
file is stored in the large file system and handles all necessary
- steps."""
+ steps.
+ """
if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):
contentTempFile = self.generateTempFile(contents)
- (pointer_git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile)
+ pointer_git_mode, contents, localLargeFile = self.generatePointer(contentTempFile)
if pointer_git_mode:
git_mode = pointer_git_mode
if localLargeFile:
sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
return (git_mode, contents)
+
class MockLFS(LargeFileSystem):
"""Mock large file system for testing."""
def generatePointer(self, contentFile):
"""The pointer content is the original content prefixed with "pointer-".
- The local filename of the large file storage is derived from the file content.
+ The local filename of the large file storage is derived from the
+ file content.
"""
with open(contentFile, 'r') as f:
content = next(f)
return (gitMode, pointerContents, localLargeFile)
def pushFile(self, localLargeFile):
- """The remote filename of the large file storage is the same as the local
- one but in a different directory.
+ """The remote filename of the large file storage is the same as the
+ local one but in a different directory.
"""
remotePath = os.path.join(os.path.dirname(localLargeFile), '..', 'remote')
if not os.path.exists(remotePath):
os.makedirs(remotePath)
shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
+
class GitLFS(LargeFileSystem):
"""Git LFS as backend for the git-p4 large file system.
- See https://git-lfs.github.com/ for details."""
+ See https://git-lfs.github.com/ for details.
+ """
def __init__(self, *args):
LargeFileSystem.__init__(self, *args)
else:
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
+
class Command:
- delete_actions = ( "delete", "move/delete", "purge" )
- add_actions = ( "add", "branch", "move/add" )
+ delete_actions = ("delete", "move/delete", "purge")
+ add_actions = ("add", "branch", "move/add")
def __init__(self):
self.usage = "usage: %prog [options]"
setattr(self, attr, value)
return getattr(self, attr)
+
class P4UserMap:
def __init__(self):
self.userMapFromPerforceServer = False
die("Could not find your p4 user id")
def p4UserIsMe(self, p4User):
- # return True if the given p4 user is actually me
+ """Return True if the given p4 user is actually me."""
me = self.p4UserId()
if not p4User or p4User != me:
return False
for output in p4CmdList(["users"]):
if "User" not in output:
continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+ # "FullName" is bytes. "Email" on the other hand might be bytes
+ # or unicode string depending on whether we are running under
+ # python2 or python3. To support
+ # git-p4.metadataDecodingStrategy=fallback, self.users dict values
+ # are always bytes, ready to be written to git.
+ emailbytes = metadata_stream_to_writable_bytes(output["Email"])
+ self.users[output["User"]] = output["FullName"] + b" <" + emailbytes + b">"
self.emails[output["Email"]] = output["User"]
mapUserConfigRegex = re.compile(r"^\s*(\S+)\s*=\s*(.+)\s*<(\S+)>\s*$", re.VERBOSE)
user = mapUser[0][0]
fullname = mapUser[0][1]
email = mapUser[0][2]
- self.users[user] = fullname + " <" + email + ">"
+ fulluser = fullname + " <" + email + ">"
+ self.users[user] = metadata_stream_to_writable_bytes(fulluser)
self.emails[email] = user
- s = ''
+ s = b''
for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+ keybytes = metadata_stream_to_writable_bytes(key)
+ s += b"%s\t%s\n" % (keybytes.expandtabs(1), val.expandtabs(1))
- open(self.getUserCacheFilename(), 'w').write(s)
+ open(self.getUserCacheFilename(), 'wb').write(s)
self.userMapFromPerforceServer = True
def loadUserMapFromCache(self):
self.users = {}
self.userMapFromPerforceServer = False
try:
- cache = open(self.getUserCacheFilename(), 'r')
+ cache = open(self.getUserCacheFilename(), 'rb')
lines = cache.readlines()
cache.close()
for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
+ entry = line.strip().split(b"\t")
+ self.users[entry[0].decode('utf_8')] = entry[1]
except IOError:
self.getUserMapFromPerforceServer()
+
class P4Submit(Command, P4UserMap):
conflict_behavior_choices = ("ask", "skip", "quit")
die("You have files opened with perforce! Close them before starting the sync.")
def separate_jobs_from_description(self, message):
- """Extract and return a possible Jobs field in the commit
- message. It goes into a separate section in the p4 change
- specification.
+ """Extract and return a possible Jobs field in the commit message. It
+ goes into a separate section in the p4 change specification.
- A jobs line starts with "Jobs:" and looks like a new field
- in a form. Values are white-space separated on the same
- line or on following lines that start with a tab.
+ A jobs line starts with "Jobs:" and looks like a new field in a
+ form. Values are white-space separated on the same line or on
+ following lines that start with a tab.
- This does not parse and extract the full git commit message
- like a p4 form. It just sees the Jobs: line as a marker
- to pass everything from then on directly into the p4 form,
- but outside the description section.
+ This does not parse and extract the full git commit message like a
+ p4 form. It just sees the Jobs: line as a marker to pass everything
+ from then on directly into the p4 form, but outside the description
+ section.
- Return a tuple (stripped log message, jobs string)."""
+ Return a tuple (stripped log message, jobs string).
+ """
m = re.search(r'^Jobs:', message, re.MULTILINE)
if m is None:
return (stripped_message, jobtext)
def prepareLogMessage(self, template, message, jobs):
- """Edits the template returned from "p4 change -o" to insert
- the message in the Description field, and the jobs text in
- the Jobs field."""
+ """Edits the template returned from "p4 change -o" to insert the
+ message in the Description field, and the jobs text in the Jobs
+ field.
+ """
result = ""
inDescriptionSection = False
return result
def patchRCSKeywords(self, file, regexp):
- # Attempt to zap the RCS keywords in a p4 controlled file matching the given regex
- (handle, outFileName) = tempfile.mkstemp(dir='.')
+ """Attempt to zap the RCS keywords in a p4 controlled file matching the
+ given regex.
+ """
+ handle, outFileName = tempfile.mkstemp(dir='.')
try:
with os.fdopen(handle, "wb") as outFile, open(file, "rb") as inFile:
for line in inFile.readlines():
print("Patched up RCS keywords in %s" % file)
- def p4UserForCommit(self,id):
- # Return the tuple (perforce user,git email) for a given git commit id
+ def p4UserForCommit(self, id):
+ """Return the tuple (perforce user,git email) for a given git commit
+ id.
+ """
self.getUserMapFromPerforceServer()
gitEmail = read_pipe(["git", "log", "--max-count=1",
"--format=%ae", id])
gitEmail = gitEmail.strip()
if gitEmail not in self.emails:
- return (None,gitEmail)
+ return (None, gitEmail)
else:
- return (self.emails[gitEmail],gitEmail)
+ return (self.emails[gitEmail], gitEmail)
- def checkValidP4Users(self,commits):
- # check if any git authors cannot be mapped to p4 users
+ def checkValidP4Users(self, commits):
+ """Check if any git authors cannot be mapped to p4 users."""
for id in commits:
- (user,email) = self.p4UserForCommit(id)
+ user, email = self.p4UserForCommit(id)
if not user:
msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
if gitConfigBool("git-p4.allowMissingP4Users"):
die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
def lastP4Changelist(self):
- # Get back the last changelist number submitted in this client spec. This
- # then gets used to patch up the username in the change. If the same
- # client spec is being used by multiple processes then this might go
- # wrong.
+ """Get back the last changelist number submitted in this client spec.
+
+ This then gets used to patch up the username in the change. If the
+ same client spec is being used by multiple processes then this might
+ go wrong.
+ """
results = p4CmdList(["client", "-o"]) # find the current client
client = None
for r in results:
die("Could not get changelist number for last submit - cannot patch up user details")
def modifyChangelistUser(self, changelist, newUser):
- # fixup the user field of a changelist after it has been submitted.
+ """Fixup the user field of a changelist after it has been submitted."""
changes = p4CmdList(["change", "-o", changelist])
if len(changes) != 1:
die("Bad output from p4 change modifying %s to user %s" %
(changelist, newUser))
c = changes[0]
- if c['User'] == newUser: return # nothing to do
+ if c['User'] == newUser:
+ # Nothing to do
+ return
c['User'] = newUser
# p4 does not understand format version 3 and above
input = marshal.dumps(c, 2)
die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
def canChangeChangelists(self):
- # check to see if we have p4 admin or super-user permissions, either of
- # which are required to modify changelists.
+ """Check to see if we have p4 admin or super-user permissions, either
+ of which are required to modify changelists.
+ """
results = p4CmdList(["protects", self.depotPath])
for r in results:
if 'perm' in r:
def prepareSubmitTemplate(self, changelist=None):
"""Run "p4 change -o" to grab a change specification template.
+
This does not use "p4 -G", as it is nice to keep the submission
template in original order, since a human might edit it.
Remove lines in the Files section that show changes to files
- outside the depot path we're committing into."""
+ outside the depot path we're committing into.
+ """
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
template = """\
# A Perforce Change Specification.
return template
def edit_template(self, template_file):
- """Invoke the editor to let the user change the submission
- message. Return true if okay to continue with the submit."""
+ """Invoke the editor to let the user change the submission message.
+
+ Return true if okay to continue with the submit.
+ """
# if configured to skip the editing part, just submit
if gitConfigBool("git-p4.skipSubmitEdit"):
for line in f.readlines():
newdiff += "+" + line
except UnicodeDecodeError:
- pass # Found non-text data and skip, since diff description should only include text
+ # Found non-text data and skip, since diff description
+ # should only include text
+ pass
f.close()
return (diff + newdiff).replace('\r\n', '\n')
print("Applying", read_pipe(["git", "show", "-s",
"--format=format:%h %s", id]))
- (p4User, gitEmail) = self.p4UserForCommit(id)
+ p4User, gitEmail = self.p4UserForCommit(id)
diff = read_pipe_lines(
["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id])
if regexp:
# this file is a possibility...look for RCS keywords.
for line in read_pipe_lines(
- ["git", "diff", "%s^..%s" % (id, id), file],
- raw=True):
+ ["git", "diff", "%s^..%s" % (id, id), file],
+ raw=True):
if regexp.search(line):
if verbose:
print("got keyword match on %s in %s in %s" % (regex.pattern, line, file))
#
logMessage = extractLogMessageFromGitCommit(id)
logMessage = logMessage.strip()
- (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
+ logMessage, jobs = self.separate_jobs_from_description(logMessage)
template = self.prepareSubmitTemplate(update_shelve)
submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
if self.preserveUser:
- submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
+ submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
if self.checkAuthorship and not self.p4UserIsMe(p4User):
submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
submitTemplate += separatorLine
submitTemplate += self.get_diff_description(editedFiles, filesToAdd, symlinks)
- (handle, fileName) = tempfile.mkstemp()
+ handle, fileName = tempfile.mkstemp()
tmpFile = os.fdopen(handle, "w+b")
if self.isWindows:
submitTemplate = submitTemplate.replace("\n", "\r\n")
print(" " + self.clientPath)
print("")
print("To submit, use \"p4 submit\" to write a new description,")
- print("or \"p4 submit -i <%s\" to use the one prepared by" \
+ print("or \"p4 submit -i <%s\" to use the one prepared by"
" \"git p4\"." % fileName)
print("You can delete the file \"%s\" when finished." % fileName)
if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
- print("To preserve change ownership by user %s, you must\n" \
- "do \"p4 change -f <change>\" after submitting and\n" \
+ print("To preserve change ownership by user %s, you must\n"
+ "do \"p4 change -f <change>\" after submitting and\n"
"edit the User field.")
if pureRenameCopy:
print("After submitting, renamed files must be re-synced.")
# Revert changes if we skip this patch
if not submitted or self.shelve:
if self.shelve:
- print ("Reverting shelved files.")
+ print("Reverting shelved files.")
else:
- print ("Submission cancelled, undoing p4 changes.")
+ print("Submission cancelled, undoing p4 changes.")
sys.stdout.flush()
for f in editedFiles | filesToDelete:
p4_revert(f)
os.remove(fileName)
return submitted
- # Export git tags as p4 labels. Create a p4 label and then tag
- # with that.
def exportGitTags(self, gitTags):
+ """Export git tags as p4 labels. Create a p4 label and then tag with
+ that.
+ """
+
validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
if len(validLabelRegexp) == 0:
validLabelRegexp = defaultLabelRegexp
# Create the label - use the same view as the client spec we are using
clientSpec = getClientSpec()
- labelTemplate = "Label: %s\n" % name
+ labelTemplate = "Label: %s\n" % name
labelTemplate += "Description:\n"
for b in body:
labelTemplate += "\t" + b + "\n"
if self.dry_run:
print("Would create p4 label %s for tag" % name)
elif self.prepare_p4_only:
- print("Not creating p4 label %s for tag due to option" \
+ print("Not creating p4 label %s for tag due to option"
" --prepare-p4-only" % name)
else:
p4_write_pipe(["label", "-i"], labelTemplate)
if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
die("%s is not in git-p4.allowSubmit" % self.master)
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
self.depotPath = settings['depot-paths'][0]
if len(self.origin) == 0:
self.origin = upstream
if not self.no_verify:
try:
if not run_git_hook("p4-pre-submit"):
- print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \
- "this pre-submission check by adding\nthe command line option '--no-verify', " \
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip "
+ "this pre-submission check by adding\nthe command line option '--no-verify', "
"however,\nthis will also skip the p4-changelist hook as well.")
sys.exit(1)
except Exception as e:
- print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\
- "with the error '{0}'".format(e.message) )
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "
+ "with the error '{0}'".format(e.message))
sys.exit(1)
#
applied.append(commit)
if self.prepare_p4_only:
if i < last:
- print("Processing only the first commit due to option" \
+ print("Processing only the first commit due to option"
" --prepare-p4-only")
break
else:
# exit with error unless everything applied perfectly
if len(commits) != len(applied):
- sys.exit(1)
+ sys.exit(1)
return True
+
class View(object):
- """Represent a p4 view ("p4 help views"), and map files in a
- repo according to the view."""
+ """Represent a p4 view ("p4 help views"), and map files in a repo according
+ to the view.
+ """
def __init__(self, client_name):
self.mappings = []
self.client_spec_path_cache = {}
def append(self, view_line):
- """Parse a view line, splitting it into depot and client
- sides. Append to self.mappings, preserving order. This
- is only needed for tag creation."""
+ """Parse a view line, splitting it into depot and client sides. Append
+ to self.mappings, preserving order. This is only needed for tag
+ creation.
+ """
# Split the view line into exactly two words. P4 enforces
# structure on these lines that simplifies this quite a bit.
return clientFile[len(self.client_prefix):]
def update_client_spec_path_cache(self, files):
- """ Caching file paths by "p4 where" batch query """
+ """Caching file paths by "p4 where" batch query."""
# List depot file paths exclude that already cached
fileArgs = [f['path'] for f in files if decode_path(f['path']) not in self.client_spec_path_cache]
self.client_spec_path_cache[depotFile] = b''
def map_in_client(self, depot_path):
- """Return the relative location in the client where this
- depot file should live. Returns "" if the file should
- not be mapped in the client."""
+ """Return the relative location in the client where this depot file
+ should live.
+
+ Returns "" if the file should not be mapped in the client.
+ """
if gitConfigBool("core.ignorecase"):
depot_path = depot_path.lower()
if depot_path in self.client_spec_path_cache:
return self.client_spec_path_cache[depot_path]
- die( "Error: %s is not found in client spec path" % depot_path )
+ die("Error: %s is not found in client spec path" % depot_path)
return ""
+
def cloneExcludeCallback(option, opt_str, value, parser):
# prepend "/" because the first "/" was consumed as part of the option itself.
# ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
+
class P4Sync(Command, P4UserMap):
def __init__(self):
self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
self.labels = {}
- # Force a checkpoint in fast-import and wait for it to finish
def checkpoint(self):
+ """Force a checkpoint in fast-import and wait for it to finish."""
self.gitStream.write("checkpoint\n\n")
self.gitStream.write("progress checkpoint\n\n")
self.gitStream.flush()
return True
return False
- def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl=0):
files = []
fnum = 0
while "depotFile%s" % fnum in commit:
- path = commit["depotFile%s" % fnum]
+ path = commit["depotFile%s" % fnum]
found = self.isPathWanted(decode_path(path))
if not found:
fnum = fnum + 1
return jobs
def stripRepoPath(self, path, prefixes):
- """When streaming files, this is called to map a p4 depot path
- to where it should go in git. The prefixes are either
- self.depotPaths, or self.branchPrefixes in the case of
- branch detection."""
+ """When streaming files, this is called to map a p4 depot path to where
+ it should go in git. The prefixes are either self.depotPaths, or
+ self.branchPrefixes in the case of branch detection.
+ """
if self.useClientSpec:
# branch detection moves files up a level (the branch name)
return path
def splitFilesIntoBranches(self, commit):
- """Look at each depotFile in the commit to figure out to what
- branch it belongs."""
+ """Look at each depotFile in the commit to figure out to what branch it
+ belongs.
+ """
if self.clientSpecDirs:
files = self.extractFilesFromCommit(commit)
print('Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, path))
return path
- # output one file from the P4 stream
- # - helper for streamP4Files
-
def streamOneP4File(self, file, contents):
+ """Output one file from the P4 stream.
+
+ This is a helper for streamP4Files().
+ """
+
file_path = file['depotFile']
relPath = self.stripRepoPath(decode_path(file_path), self.branchPrefixes)
if 'fileSize' in self.stream_file:
size = int(self.stream_file['fileSize'])
else:
- size = 0 # deleted files don't get a fileSize apparently
+ # Deleted files don't get a fileSize apparently
+ size = 0
sys.stdout.write('\r%s --> %s (%s)\n' % (
file_path, relPath, format_size_human_readable(size)))
sys.stdout.flush()
- (type_base, type_mods) = split_p4_type(file["type"])
+ type_base, type_mods = split_p4_type(file["type"])
git_mode = "100644"
if "x" in type_mods:
else:
if p4_version_string().find('/NT') >= 0:
text = text.replace(b'\r\n', b'\n')
- contents = [ text ]
+ contents = [text]
if type_base == "apple":
# Apple filetype files will be streamed as a concatenation of
print("\nIgnoring apple filetype file %s" % file['depotFile'])
return
+ if type_base == "utf8":
+ # The type utf8 explicitly means utf8 *with BOM*. These are
+ # streamed just like regular text files, however, without
+ # the BOM in the stream.
+ # Therefore, to accurately import these files into git, we
+ # need to explicitly re-add the BOM before writing.
+ # 'contents' is a set of bytes in this case, so create the
+ # BOM prefix as a b'' literal.
+ contents = [b'\xef\xbb\xbf' + contents[0]] + contents[1:]
+
# Note that we do not try to de-mangle keywords on utf16 files,
# even though in theory somebody may want that.
regexp = p4_keywords_regexp_for_type(type_base, type_mods)
contents = [regexp.sub(br'$\1$', c) for c in contents]
if self.largeFileSystem:
- (git_mode, contents) = self.largeFileSystem.processContent(git_mode, relPath, contents)
+ git_mode, contents = self.largeFileSystem.processContent(git_mode, relPath, contents)
self.writeToGitStream(git_mode, relPath, contents)
if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
self.largeFileSystem.removeLargeFile(relPath)
- # handle another chunk of streaming data
def streamP4FilesCb(self, marshalled):
+ """Handle another chunk of streaming data."""
# catch p4 errors and complain
err = None
self.stream_file[k] = marshalled[k]
if (verbose and
- 'streamContentSize' in self.stream_file and
- 'fileSize' in self.stream_file and
- 'depotFile' in self.stream_file):
+ 'streamContentSize' in self.stream_file and
+ 'fileSize' in self.stream_file and
+ 'depotFile' in self.stream_file):
size = int(self.stream_file["fileSize"])
if size > 0:
progress = 100*self.stream_file['streamContentSize']/size
self.stream_have_file_info = True
- # Stream directly from "p4 files" into "git fast-import"
def streamP4Files(self, files):
+ """Stream directly from "p4 files" into "git fast-import."""
+
filesForCommit = []
filesToRead = []
filesToDelete = []
if userid in self.users:
return self.users[userid]
else:
- return "%s <a@b>" % userid
+ userid_bytes = metadata_stream_to_writable_bytes(userid)
+ return b"%s <a@b>" % userid_bytes
def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
- """ Stream a p4 tag.
- commit is either a git commit, or a fast-import mark, ":<p4commit>"
- """
+ """Stream a p4 tag.
+
+ Commit is either a git commit, or a fast-import mark, ":<p4commit>".
+ """
if verbose:
print("writing tag %s for commit %s" % (labelName, commit))
email = self.make_email(owner)
else:
email = self.make_email(self.p4UserId())
- tagger = "%s %s %s" % (email, epoch, self.tz)
- gitStream.write("tagger %s\n" % tagger)
+ gitStream.write("tagger ")
+ gitStream.write(email)
+ gitStream.write(" %s %s\n" % (epoch, self.tz))
- print("labelDetails=",labelDetails)
+ print("labelDetails=", labelDetails)
if 'Description' in labelDetails:
description = labelDetails['Description']
else:
return hasPrefix
def findShadowedFiles(self, files, change):
- # Perforce allows you commit files and directories with the same name,
- # so you could have files //depot/foo and //depot/foo/bar both checked
- # in. A p4 sync of a repository in this state fails. Deleting one of
- # the files recovers the repository.
- #
- # Git will not allow the broken state to exist and only the most recent
- # of the conflicting names is left in the repository. When one of the
- # conflicting files is deleted we need to re-add the other one to make
- # sure the git repository recovers in the same way as perforce.
+ """Perforce allows you commit files and directories with the same name,
+ so you could have files //depot/foo and //depot/foo/bar both checked
+ in. A p4 sync of a repository in this state fails. Deleting one of
+ the files recovers the repository.
+
+ Git will not allow the broken state to exist and only the most
+ recent of the conflicting names is left in the repository. When one
+ of the conflicting files is deleted we need to re-add the other one
+ to make sure the git repository recovers in the same way as
+ perforce.
+ """
+
deleted = [f for f in files if f['action'] in self.delete_actions]
to_check = set()
for f in deleted:
'rev': record['headRev'],
'type': record['headType']})
- def commit(self, details, files, branch, parent = "", allow_empty=False):
+ def commit(self, details, files, branch, parent="", allow_empty=False):
epoch = details["time"]
author = details["user"]
jobs = self.extractJobsFromCommit(details)
self.gitStream.write("commit %s\n" % branch)
self.gitStream.write("mark :%s\n" % details["change"])
self.committedChanges.add(int(details["change"]))
- committer = ""
if author not in self.users:
self.getUserMapFromPerforceServer()
- committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
- self.gitStream.write("committer %s\n" % committer)
+ self.gitStream.write("committer ")
+ self.gitStream.write(self.make_email(author))
+ self.gitStream.write(" %s %s\n" % (epoch, self.tz))
self.gitStream.write("data <<EOT\n")
self.gitStream.write(details["desc"])
print("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
- # Build a dictionary of changelists and labels, for "detect-labels" option.
def getLabels(self):
+ """Build a dictionary of changelists and labels, for "detect-labels"
+ option.
+ """
+
self.labels = {}
l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
if self.verbose:
print("Label changes: %s" % self.labels.keys())
- # Import p4 labels as git tags. A direct mapping does not
- # exist, so assume that if all the files are at the same revision
- # then we can use that, or it's something more complicated we should
- # just ignore.
def importP4Labels(self, stream, p4Labels):
+ """Import p4 labels as git tags. A direct mapping does not exist, so
+ assume that if all the files are at the same revision then we can
+ use that, or it's something more complicated we should just ignore.
+ """
+
if verbose:
print("import p4 labels: " + ' '.join(p4Labels))
if not m.match(name):
if verbose:
- print("label %s does not match regexp %s" % (name,validLabelRegexp))
+ print("label %s does not match regexp %s" % (name, validLabelRegexp))
continue
if name in ignoredP4Labels:
p = p[:-1]
p = p[p.strip().rfind("/") + 1:]
if not p.endswith("/"):
- p += "/"
+ p += "/"
return p
def getBranchMapping(self):
continue
source = paths[0]
destination = paths[1]
- ## HACK
+ # HACK
if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
source = source[len(self.depotPaths[0]):-4]
destination = destination[len(self.depotPaths[0]):-4]
configBranches = gitConfigList("git-p4.branchList")
for branch in configBranches:
if branch:
- (source, destination) = branch.split(":")
+ source, destination = branch.split(":")
self.knownBranches[destination] = source
lostAndFoundBranches.discard(destination)
if source not in self.knownBranches:
lostAndFoundBranches.add(source)
-
for branch in lostAndFoundBranches:
self.knownBranches[branch] = branch
def importNewBranch(self, branch, maxChange):
# make fast-import flush all changes to disk and update the refs using the checkpoint
# command so that we can try to find the branch parent in the git history
- self.gitStream.write("checkpoint\n\n");
- self.gitStream.flush();
+ self.gitStream.write("checkpoint\n\n")
+ self.gitStream.flush()
branchPrefix = self.depotPaths[0] + branch + "/"
range = "@1,%s" % maxChange
- #print "prefix" + branchPrefix
changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
if len(changes) <= 0:
return False
firstChange = changes[0]
- #print "first change in branch: %s" % firstChange
sourceBranch = self.knownBranches[branch]
sourceDepotPath = self.depotPaths[0] + sourceBranch
sourceRef = self.gitRefForBranch(sourceBranch)
- #print "source " + sourceBranch
branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
- #print "branch parent: %s" % branchParentChange
gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
if len(gitParent) > 0:
self.initialParents[self.gitRefForBranch(branch)] = gitParent
- #print "parent git commit: %s" % gitParent
self.importChanges(changes)
return True
if self.detectBranches:
branches = self.splitFilesIntoBranches(description)
for branch in branches.keys():
- ## HACK --hwn
+ # HACK --hwn
branchPrefix = self.depotPaths[0] + branch + "/"
- self.branchPrefixes = [ branchPrefix ]
+ self.branchPrefixes = [branchPrefix]
parent = ""
fullBranch = self.projectName + branch
if fullBranch not in self.p4BranchesInGit:
if not self.silent:
- print("\n Importing new branch %s" % fullBranch);
+ print("\n Importing new branch %s" % fullBranch)
if self.importNewBranch(branch, change - 1):
parent = ""
self.p4BranchesInGit.append(fullBranch)
if not self.silent:
- print("\n Resuming with change %s" % change);
+ print("\n Resuming with change %s" % change)
if self.verbose:
print("parent determined through known branches: %s" % parent)
newestRevision = 0
fileCnt = 0
- fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+ fileArgs = ["%s...%s" % (p, revision) for p in self.depotPaths]
for info in p4CmdList(["files"] + fileArgs):
% info['data'])
if info['data'].find("must refer to client") >= 0:
sys.stderr.write("This particular p4 error is misleading.\n")
- sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Perhaps the depot path was misspelled.\n")
sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
sys.exit(1)
if 'p4ExitCode' in info:
sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
sys.exit(1)
-
change = int(info["change"])
if change > newestRevision:
newestRevision = change
if info["action"] in self.delete_actions:
- # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
- #fileCnt = fileCnt + 1
continue
- for prop in ["depotFile", "rev", "action", "type" ]:
+ for prop in ["depotFile", "rev", "action", "type"]:
details["%s%s" % (prop, fileCnt)] = info[prop]
fileCnt = fileCnt + 1
print("IO error details: {}".format(err))
print(self.gitError.read())
-
def importRevisions(self, args, branch_arg_given):
changes = []
self.importProcess = subprocess.Popen(["git", "fast-import"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE);
+ stderr=subprocess.PIPE)
self.gitOutput = self.importProcess.stdout
self.gitStream = self.importProcess.stdin
self.gitError = self.importProcess.stderr
# restrict to just this one, disabling detect-branches
if branch_arg_given:
- short = self.branch.split("/")[-1]
+ short = shortP4Ref(self.branch, self.importIntoRemotes)
if short in branches:
- self.p4BranchesInGit = [ short ]
+ self.p4BranchesInGit = [short]
+ elif self.branch.startswith('refs/') and \
+ branchExists(self.branch) and \
+ '[git-p4:' in extractLogMessageFromGitCommit(self.branch):
+ self.p4BranchesInGit = [self.branch]
else:
self.p4BranchesInGit = branches.keys()
p4Change = 0
for branch in self.p4BranchesInGit:
- logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
+ logMsg = extractLogMessageFromGitCommit(fullP4Ref(branch,
+ self.importIntoRemotes))
settings = extractSettingsGitLog(logMsg)
self.readOptions(settings)
- if ('depot-paths' in settings
- and 'change' in settings):
+ if 'depot-paths' in settings and 'change' in settings:
change = int(settings['change']) + 1
p4Change = max(p4Change, change)
i = i - 1
break
- paths.append ("/".join(cur_list[:i + 1]))
+ paths.append("/".join(cur_list[:i + 1]))
self.previousDepotPaths = paths
if not self.silent and not self.detectBranches:
print("Performing incremental import into %s git branch" % self.branch)
- # accept multiple ref name abbreviations:
- # refs/foo/bar/branch -> use it exactly
- # p4/branch -> prepend refs/remotes/ or refs/heads/
- # branch -> prepend refs/remotes/p4/ or refs/heads/p4/
- if not self.branch.startswith("refs/"):
- if self.importIntoRemotes:
- prepend = "refs/remotes/"
- else:
- prepend = "refs/heads/"
- if not self.branch.startswith("p4/"):
- prepend += "p4/"
- self.branch = prepend + self.branch
+ self.branch = fullP4Ref(self.branch, self.importIntoRemotes)
if len(args) == 0 and self.depotPaths:
if not self.silent:
else:
if self.depotPaths and self.depotPaths != args:
print("previous import used depot path %s and now %s was specified. "
- "This doesn't work!" % (' '.join (self.depotPaths),
- ' '.join (args)))
+ "This doesn't work!" % (' '.join(self.depotPaths),
+ ' '.join(args)))
sys.exit(1)
self.depotPaths = sorted(args)
if len(self.changesFile) == 0:
revision = "#head"
- p = re.sub ("\.\.\.$", "", p)
+ p = re.sub("\.\.\.$", "", p)
if not p.endswith("/"):
p += "/"
self.loadUserMapFromCache()
self.labels = {}
if self.detectLabels:
- self.getLabels();
+ self.getLabels()
if self.detectBranches:
- ## FIXME - what's a P4 projectName ?
+ # FIXME - what's a P4 projectName ?
self.projectName = self.guessProjectName()
if self.hasOrigin:
for b in self.p4BranchesInGit:
if b != "master":
- ## FIXME
+ # FIXME
b = b[len(self.projectName):]
self.createdBranches.add(b)
return True
+
class P4Rebase(Command):
def __init__(self):
Command.__init__(self)
def rebase(self):
if os.system("git update-index --refresh") != 0:
- die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.");
+ die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.")
if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0:
- die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
+ die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.")
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
if len(upstream) == 0:
die("Cannot find upstream branchpoint for rebase")
"HEAD", "--"])
return True
+
class P4Clone(P4Sync):
def __init__(self):
P4Sync.__init__(self)
self.cloneBare = False
def defaultDestination(self, args):
- ## TODO: use common prefix of args?
+ # TODO: use common prefix of args?
depotPath = args[0]
depotDir = re.sub("(@[^@]*)$", "", depotPath)
depotDir = re.sub("(#[^#]*)$", "", depotDir)
os.makedirs(self.cloneDestination)
chdir(self.cloneDestination)
- init_cmd = [ "git", "init" ]
+ init_cmd = ["git", "init"]
if self.cloneBare:
init_cmd.append("--bare")
retcode = subprocess.call(init_cmd)
# create a master branch and check out a work tree
if gitBranchExists(self.branch):
- system([ "git", "branch", currentGitBranch(), self.branch ])
+ system(["git", "branch", currentGitBranch(), self.branch])
if not self.cloneBare:
- system([ "git", "checkout", "-f" ])
+ system(["git", "checkout", "-f"])
else:
- print('Not checking out any branch, use ' \
+ print('Not checking out any branch, use '
'"git checkout -q -b master <branch>"')
# auto-set this variable if invoked with --use-client-spec
if self.useClientSpec_from_options:
system(["git", "config", "--bool", "git-p4.useclientspec", "true"])
+ # persist any git-p4 encoding-handling config options passed in for clone:
+ if gitConfig('git-p4.metadataDecodingStrategy'):
+ system(["git", "config", "git-p4.metadataDecodingStrategy", gitConfig('git-p4.metadataDecodingStrategy')])
+ if gitConfig('git-p4.metadataFallbackEncoding'):
+ system(["git", "config", "git-p4.metadataFallbackEncoding", gitConfig('git-p4.metadataFallbackEncoding')])
+ if gitConfig('git-p4.pathEncoding'):
+ system(["git", "config", "git-p4.pathEncoding", gitConfig('git-p4.pathEncoding')])
+
return True
+
class P4Unshelve(Command):
def __init__(self):
Command.__init__(self)
self.destbranch = "refs/remotes/p4-unshelved"
def renameBranch(self, branch_name):
- """ Rename the existing branch to branch_name.N
- """
+ """Rename the existing branch to branch_name.N ."""
found = True
- for i in range(0,1000):
+ for i in range(0, 1000):
backup_branch_name = "{0}.{1}".format(branch_name, i)
if not gitBranchExists(backup_branch_name):
- gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
+ # Copy ref to backup
+ gitUpdateRef(backup_branch_name, branch_name)
gitDeleteRef(branch_name)
found = True
print("renamed old unshelve branch to {0}".format(backup_branch_name))
sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
def findLastP4Revision(self, starting_point):
- """ Look back from starting_point for the first commit created by git-p4
- to find the P4 commit we are based on, and the depot-paths.
- """
+ """Look back from starting_point for the first commit created by git-p4
+ to find the P4 commit we are based on, and the depot-paths.
+ """
for parent in (range(65535)):
log = extractLogMessageFromGitCommit("{0}~{1}".format(starting_point, parent))
sys.exit("could not find git-p4 commits in {0}".format(self.origin))
def createShelveParent(self, change, branch_name, sync, origin):
- """ Create a commit matching the parent of the shelved changelist 'change'
- """
+ """Create a commit matching the parent of the shelved changelist
+ 'change'.
+ """
parent_description = p4_describe(change, shelved=True)
parent_description['desc'] = 'parent for shelved changelist {}\n'.format(change)
files = sync.extractFilesFromCommit(parent_description, shelved=False, shelved_cl=change)
return True
+
class P4Branches(Command):
def __init__(self):
Command.__init__(self)
- self.options = [ ]
+ self.options = []
self.description = ("Shows the git branches that hold imports and their "
+ "corresponding perforce depot paths")
self.verbose = False
print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]))
return True
+
class HelpFormatter(optparse.IndentedHelpFormatter):
def __init__(self):
optparse.IndentedHelpFormatter.__init__(self)
else:
return ""
+
def printUsage(commands):
print("usage: %s <command> [options]" % sys.argv[0])
print("")
print("Try %s <command> --help for command specific help." % sys.argv[0])
print("")
+
commands = {
- "submit" : P4Submit,
- "commit" : P4Submit,
- "sync" : P4Sync,
- "rebase" : P4Rebase,
- "clone" : P4Clone,
- "branches" : P4Branches,
- "unshelve" : P4Unshelve,
+ "submit": P4Submit,
+ "commit": P4Submit,
+ "sync": P4Sync,
+ "rebase": P4Rebase,
+ "clone": P4Clone,
+ "branches": P4Branches,
+ "unshelve": P4Unshelve,
}
+
def main():
if len(sys.argv[1:]) == 0:
printUsage(commands.keys())
parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
options,
- description = cmd.description,
- formatter = HelpFormatter())
+ description=cmd.description,
+ formatter=HelpFormatter())
try:
- (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+ cmd, args = parser.parse_args(sys.argv[2:], cmd)
except:
parser.print_help()
raise
global verbose
verbose = cmd.verbose
if cmd.needsGit:
- if cmd.gitdir == None:
+ if cmd.gitdir is None:
cmd.gitdir = os.path.abspath(".git")
if not isValidGitDir(cmd.gitdir):
# "rev-parse --git-dir" without arguments will try $PWD/.git
if os.path.exists(cmd.gitdir):
cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip()
if len(cdup) > 0:
- chdir(cdup);
+ chdir(cdup)
if not isValidGitDir(cmd.gitdir):
if isValidGitDir(cmd.gitdir + "/.git"):
};
const char git_usage_string[] =
- N_("git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
+ N_("git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]\n"
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
* commands can be written with "--" prepended
* to make them look like flags.
*/
- if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
+ if (!strcmp(cmd, "--help") || !strcmp(cmd, "-h") ||
+ !strcmp(cmd, "--version") || !strcmp(cmd, "-v"))
break;
/*
argc--;
handle_options(&argv, &argc, NULL);
if (argc > 0) {
- /* translate --help and --version into commands */
- skip_prefix(argv[0], "--", &argv[0]);
+ if (!strcmp("--version", argv[0]) || !strcmp("-v", argv[0]))
+ argv[0] = "version";
+ else if (!strcmp("--help", argv[0]) || !strcmp("-h", argv[0]))
+ argv[0] = "help";
} else {
/* The user didn't specify a command; give them help */
commit_pager_choice();
proc resizeclistpanes {win w} {
global oldwidth oldsash use_ttk
if {[info exists oldwidth($win)]} {
- if {[info exists oldsash($win)]} {
- set s0 [lindex $oldsash($win) 0]
- set s1 [lindex $oldsash($win) 1]
+ if {[info exists oldsash($win)]} {
+ set s0 [lindex $oldsash($win) 0]
+ set s1 [lindex $oldsash($win) 1]
} elseif {$use_ttk} {
set s0 [$win sashpos 0]
set s1 [$win sashpos 1]
} else {
$win sash place 0 $sash0 [lindex $s0 1]
$win sash place 1 $sash1 [lindex $s1 1]
+ set sash0 [list $sash0 [lindex $s0 1]]
+ set sash1 [list $sash1 [lindex $s1 1]]
}
- set oldsash($win) [list $sash0 $sash1]
+ set oldsash($win) [list $sash0 $sash1]
}
set oldwidth($win) $w
}
proc resizecdetpanes {win w} {
global oldwidth oldsash use_ttk
if {[info exists oldwidth($win)]} {
- if {[info exists oldsash($win)]} {
- set s0 $oldsash($win)
+ if {[info exists oldsash($win)]} {
+ set s0 $oldsash($win)
} elseif {$use_ttk} {
set s0 [$win sashpos 0]
} else {
$win sashpos 0 $sash0
} else {
$win sash place 0 $sash0 [lindex $s0 1]
+ set sash0 [list $sash0 [lindex $s0 1]]
}
- set oldsash($win) $sash0
+ set oldsash($win) $sash0
}
set oldwidth($win) $w
}
http_init(NULL, url, 0);
preq = new_direct_http_pack_request(packfile_hash->hash, xstrdup(url));
- if (preq == NULL)
+ if (!preq)
die("couldn't create http pack request");
preq->slot->results = &results;
preq->index_pack_args = index_pack_args;
struct http_object_request *obj_req;
obj_req = new_http_object_request(repo->url, &request->obj->oid);
- if (obj_req == NULL) {
+ if (!obj_req) {
request->state = ABORTED;
return;
}
fprintf(stderr, " which contains %s\n", oid_to_hex(&request->obj->oid));
preq = new_http_pack_request(target->hash, repo->url);
- if (preq == NULL) {
+ if (!preq) {
repo->can_update_info_refs = 0;
return;
}
/* Keep locks active */
check_locks();
- if (request->headers != NULL)
+ if (request->headers)
curl_slist_free_all(request->headers);
/* URL is reused for MOVE after PUT and used during FETCH */
const char *c = strchr(name, ':');
int old_namelen, new_len;
- if (c == NULL)
+ if (!c)
c = name;
else
c++;
ctx->userFunc(ctx, 1);
- if (c == NULL)
+ if (!c)
c = name;
else
c++;
/* Lock remote branch ref */
ref_lock = lock_remote(ref->name, LOCK_TIME);
- if (ref_lock == NULL) {
+ if (!ref_lock) {
fprintf(stderr, "Unable to lock remote branch %s\n",
ref->name);
if (helper_status)
struct http_object_request *req;
req = new_http_object_request(obj_req->repo->base, &obj_req->oid);
- if (req == NULL) {
+ if (!req) {
obj_req->state = ABORTED;
return;
}
/* Use alternates if necessary */
if (missing_target(obj_req->req)) {
fetch_alternates(walker, alt->base);
- if (obj_req->repo->next != NULL) {
+ if (obj_req->repo->next) {
obj_req->repo =
obj_req->repo->next;
release_http_object_request(obj_req->req);
alt_req->url->buf);
active_requests++;
slot->in_use = 1;
- if (slot->finished != NULL)
+ if (slot->finished)
(*slot->finished) = 0;
if (!start_active_slot(slot)) {
cdata->got_alternates = -1;
slot->in_use = 0;
- if (slot->finished != NULL)
+ if (slot->finished)
(*slot->finished) = 1;
}
return;
}
preq = new_http_pack_request(target->hash, repo->base);
- if (preq == NULL)
+ if (!preq)
goto abort;
preq->slot->results = &results;
if (hasheq(obj_req->oid.hash, hash))
break;
}
- if (obj_req == NULL)
+ if (!obj_req)
return error("Couldn't find request for %s in the queue", hex);
if (has_object_file(&obj_req->oid)) {
- if (obj_req->req != NULL)
+ if (obj_req->req)
abort_http_object_request(obj_req->req);
abort_object_request(obj_req);
return 0;
closedown_active_slot(slot);
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
- if (slot->finished != NULL)
+ if (slot->finished)
(*slot->finished) = 1;
/* Store slot results so they can be read after the slot is reused */
- if (slot->results != NULL) {
+ if (slot->results) {
slot->results->curl_result = slot->curl_result;
slot->results->http_code = slot->http_code;
curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
}
/* Run callback if appropriate */
- if (slot->callback_func != NULL)
+ if (slot->callback_func)
slot->callback_func(slot->callback_data);
}
while (slot != NULL &&
slot->curl != curl_message->easy_handle)
slot = slot->next;
- if (slot != NULL) {
+ if (slot) {
xmulti_remove_handle(slot);
slot->curl_result = curl_result;
finish_active_slot(slot);
curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST,
ssl_cipherlist);
- if (ssl_cert != NULL)
+ if (ssl_cert)
curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
if (has_cert_password())
curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
- if (ssl_key != NULL)
+ if (ssl_key)
curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
- if (ssl_capath != NULL)
+ if (ssl_capath)
curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
- if (ssl_pinnedkey != NULL)
+ if (ssl_pinnedkey)
curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey);
#endif
if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL);
#endif
} else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) {
- if (ssl_cainfo != NULL)
+ if (ssl_cainfo)
curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO
- if (http_proxy_ssl_ca_info != NULL)
+ if (http_proxy_ssl_ca_info)
curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
#endif
}
{
char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
- if (http_max_requests != NULL)
+ if (http_max_requests)
max_requests = atoi(http_max_requests);
}
set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
- if (low_speed_limit != NULL)
+ if (low_speed_limit)
curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
- if (low_speed_time != NULL)
+ if (low_speed_time)
curl_low_speed_time = strtol(low_speed_time, NULL, 10);
if (curl_ssl_verify == -1)
while (slot != NULL) {
struct active_request_slot *next = slot->next;
- if (slot->curl != NULL) {
+ if (slot->curl) {
xmulti_remove_handle(slot);
curl_easy_cleanup(slot->curl);
}
free((void *)http_proxy_authmethod);
http_proxy_authmethod = NULL;
- if (cert_auth.password != NULL) {
+ if (cert_auth.password) {
memset(cert_auth.password, 0, strlen(cert_auth.password));
FREE_AND_NULL(cert_auth.password);
}
ssl_cert_password_required = 0;
- if (proxy_cert_auth.password != NULL) {
+ if (proxy_cert_auth.password) {
memset(proxy_cert_auth.password, 0, strlen(proxy_cert_auth.password));
FREE_AND_NULL(proxy_cert_auth.password);
}
while (slot != NULL && slot->in_use)
slot = slot->next;
- if (slot == NULL) {
+ if (!slot) {
newslot = xmalloc(sizeof(*newslot));
newslot->curl = NULL;
newslot->in_use = 0;
newslot->next = NULL;
slot = active_queue_head;
- if (slot == NULL) {
+ if (!slot) {
active_queue_head = newslot;
} else {
while (slot->next != NULL)
slot = newslot;
}
- if (slot->curl == NULL) {
+ if (!slot->curl) {
slot->curl = curl_easy_duphandle(curl_default);
curl_session_count++;
}
slot = get_active_slot();
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- if (result == NULL) {
+ if (!result) {
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
} else {
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
void release_http_pack_request(struct http_pack_request *preq)
{
- if (preq->packfile != NULL) {
+ if (preq->packfile) {
fclose(preq->packfile);
preq->packfile = NULL;
}
void process_http_object_request(struct http_object_request *freq)
{
- if (freq->slot == NULL)
+ if (!freq->slot)
return;
freq->curl_result = freq->slot->curl_result;
freq->http_code = freq->slot->http_code;
freq->localfile = -1;
}
FREE_AND_NULL(freq->url);
- if (freq->slot != NULL) {
+ if (freq->slot) {
freq->slot->callback_func = NULL;
freq->slot->callback_data = NULL;
release_active_slot(freq->slot);
next[i] = NULL;
treenext(kwset->trie->links, next);
- if ((trans = kwset->trans) != NULL)
+ if ((trans = kwset->trans))
for (i = 0; i < NCHAR; ++i)
kwset->next[i] = next[U(trans[i])];
else
}
/* Fix things up for any translation table. */
- if ((trans = kwset->trans) != NULL)
+ if ((trans = kwset->trans))
for (i = 0; i < NCHAR; ++i)
kwset->delta[i] = delta[U(trans[i])];
else
dict[4].placeholder = "P"; dict[4].value = path_sq.buf;
dict[5].placeholder = NULL; dict[5].value = NULL;
- if (fn->cmdline == NULL)
+ if (!fn->cmdline)
die("custom merge driver %s lacks command line.", fn->name);
result->ptr = NULL;
const struct string_list_item *item)
{
int matched = 0;
- if (item->util == NULL) {
+ if (!item->util) {
if (!wildmatch(item->string, refname, 0))
matched = 1;
} else {
continue;
}
last_nonblank = c;
- if (first_nonblank == NULL)
+ if (!first_nonblank)
first_nonblank = c;
if (*c == '-') {
in_perforation = 1;
*/
lines = strbuf_split(line, '\n');
for (it = lines; (sb = *it); it++) {
- if (*(it + 1) == NULL) /* The last line */
+ if (!*(it + 1)) /* The last line */
if (sb->buf[sb->len - 1] != '\n') {
/* Partial line, save it for later. */
strbuf_addbuf(&prev, sb);
struct mailmap_entry *me;
struct string_list_item *item;
- if (old_email == NULL) {
+ if (!old_email) {
old_email = new_email;
new_email = NULL;
}
item->util = me;
}
- if (old_name == NULL) {
+ if (!old_name) {
debug_mm("mailmap: adding (simple) entry for '%s'\n", old_email);
/* Replace current name and new email for simple entry */
char *left, *right, *nstart, *nend;
*name = *email = NULL;
- if ((left = strchr(buffer, '<')) == NULL)
+ if (!(left = strchr(buffer, '<')))
return NULL;
- if ((right = strchr(left+1, '>')) == NULL)
+ if (!(right = strchr(left + 1, '>')))
return NULL;
if (!allow_empty_email && (left+1 == right))
return NULL;
if (buffer[0] == '#')
return;
- if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL)
+ if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)))
parse_name_and_email(name2, &name2, &email2, 1);
if (email1)
(int)*emaillen, debug_str(*email));
item = lookup_prefix(map, *email, *emaillen);
- if (item != NULL) {
+ if (item) {
me = (struct mailmap_entry *)item->util;
if (me->namemap.nr) {
/*
item = subitem;
}
}
- if (item != NULL) {
+ if (item) {
struct mailmap_info *mi = (struct mailmap_info *)item->util;
if (mi->name == NULL && mi->email == NULL) {
debug_mm("map_user: -- (no simple mapping)\n");
* to ensure that's the case.
*/
c_info = strmap_get(collisions, new_path);
- if (c_info == NULL)
+ if (!c_info)
BUG("c_info is NULL");
/*
}
merged_merge_bases = pop_commit(&merge_bases);
- if (merged_merge_bases == NULL) {
+ if (!merged_merge_bases) {
/* if there is no common ancestor, use an empty tree */
struct tree *tree;
{
struct dir_rename_entry key;
- if (dir == NULL)
+ if (!dir)
return NULL;
hashmap_entry_init(&key.ent, strhash(dir));
key.dir = dir;
* renamed means the root directory can never be renamed -- because
* the root directory always exists).
*/
- if (end_of_old == NULL)
+ if (!end_of_old)
return; /* Note: *old_dir and *new_dir are still NULL */
/*
* If new_path contains no directory (end_of_new is NULL), then we
* have a rename of old_path's directory to the root directory.
*/
- if (end_of_new == NULL) {
+ if (!end_of_new) {
*old_dir = xstrndup(old_path, end_of_old - old_path);
*new_dir = xstrdup("");
return;
* to ensure that's the case.
*/
collision_ent = collision_find_entry(collisions, new_path);
- if (collision_ent == NULL)
+ if (!collision_ent)
BUG("collision_ent is NULL");
/*
const struct rename *re;
int i;
- if (rename == NULL)
+ if (!rename)
return;
for (i = 0; i < rename->nr; i++) {
}
merged_merge_bases = pop_commit(&merge_bases);
- if (merged_merge_bases == NULL) {
+ if (!merged_merge_bases) {
/* if there is no common ancestor, use an empty tree */
struct tree *tree;
"$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use Araxis Merge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use Araxis Merge (requires a graphical session)"
+}
+
translate_merge_tool_path() {
echo compare
}
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Beyond Compare (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use Beyond Compare (requires a graphical session)"
+}
+
translate_merge_tool_path() {
if type bcomp >/dev/null 2>/dev/null
then
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Code Compare (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use Code Compare (requires a graphical session)"
+}
+
translate_merge_tool_path() {
if merge_mode
then
"$merge_tool_path" "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use DeltaWalker (requires a graphical session)"
+}
+
merge_cmd () {
# Adding $(pwd)/ in front of $MERGED should not be necessary.
# However without it, DeltaWalker (at least v1.9.8 on Windows)
fi >/dev/null 2>&1
}
+merge_cmd_help () {
+ echo "Use DeltaWalker (requires a graphical session)"
+}
+
translate_merge_tool_path () {
echo DeltaWalker
}
"$merge_tool_path" "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use DiffMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use DiffMerge (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
}
+diff_cmd_help () {
+ echo "Use Diffuse (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
"$LOCAL" "$MERGED" "$REMOTE" | cat
fi
}
+
+merge_cmd_help () {
+ echo "Use Diffuse (requires a graphical session)"
+}
"$merge_tool_path" --default --mode=diff2 "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use ECMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
--default --mode=merge2 --to="$MERGED"
fi
}
+
+merge_cmd_help () {
+ echo "Use ECMerge (requires a graphical session)"
+}
"$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Emacs' Emerge"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use Emacs' Emerge"
+}
+
translate_merge_tool_path() {
echo emacs
}
"$merge_tool_path" "$LOCAL" "$REMOTE" -nh
}
+diff_cmd_help () {
+ echo "Use ExamDiff Pro (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use ExamDiff Pro (requires a graphical session)"
+}
+
translate_merge_tool_path() {
mergetool_find_win32_cmd "ExamDiff.com" "ExamDiff Pro"
}
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Guiffy's Diff Tool (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use Guiffy's Diff Tool (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
"$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use KDiff3 (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
fi
}
+merge_cmd_help () {
+ echo "Use KDiff3 (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
return 1
}
+diff_cmd_help () {
+ echo "Use Kompare (requires a graphical session)"
+}
+
diff_cmd () {
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+merge_cmd_help () {
+ echo "Use Kompare (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Meld (requires a graphical session)"
+}
+
merge_cmd () {
check_meld_for_features
fi
}
+merge_cmd_help () {
+ echo "Use Meld (requires a graphical session) with optional \`auto merge\` (see \`git help mergetool\`'s \`CONFIGURATION\` section)"
+}
+
# Get meld help message
init_meld_help_msg () {
if test -z "$meld_help_msg"
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
}
+diff_cmd_help () {
+ echo "Use FileMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
-merge "$MERGED" | cat
fi
}
+
+merge_cmd_help () {
+ echo "Use FileMerge (requires a graphical session)"
+}
fi
}
+diff_cmd_help () {
+ echo "Use HelixCore P4Merge (requires a graphical session)"
+}
+
merge_cmd () {
if ! $base_present
then
printf "%s" "$empty_file"
}
+
+merge_cmd_help () {
+ echo "Use HelixCore P4Merge (requires a graphical session)"
+}
"$merge_tool_path" mergetool "$LOCAL" "$REMOTE" -o "$MERGED"
}
+diff_cmd_help () {
+ echo "Use Sublime Merge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
"$merge_tool_path" mergetool "$LOCAL" "$REMOTE" -o "$MERGED"
fi
}
+
+merge_cmd_help () {
+ echo "Use Sublime Merge (requires a graphical session)"
+}
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use TkDiff (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
exit_code_trustable () {
true
}
+
+merge_cmd_help () {
+ echo "Use TkDiff (requires a graphical session)"
+}
return 1
}
+diff_cmd_help () {
+ echo "Use TortoiseMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
echo tortoisemerge
fi
}
+
+merge_cmd_help () {
+ echo "Use TortoiseMerge (requires a graphical session)"
+}
+# This script can be run in two different contexts:
+#
+# - From git, when the user invokes the "vimdiff" merge tool. In this context
+# this script expects the following environment variables (among others) to
+# be defined (which is something "git" takes care of):
+#
+# - $BASE
+# - $LOCAL
+# - $REMOTE
+# - $MERGED
+#
+# In this mode, all this script does is to run the next command:
+#
+# vim -f -c ... $LOCAL $BASE $REMOTE $MERGED
+#
+# ...where the "..." string depends on the value of the
+# "mergetool.vimdiff.layout" configuration variable and is used to open vim
+# with a certain layout of buffers, windows and tabs.
+#
+# - From a script inside the unit tests framework folder ("t" folder) by
+# sourcing this script and then manually calling "run_unit_tests", which
+# will run a battery of unit tests to make sure nothing breaks.
+# In this context this script does not expect any particular environment
+# variable to be set.
+
+
+################################################################################
+## Internal functions (not meant to be used outside this script)
+################################################################################
+
+debug_print () {
+ # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF is set
+ # to "true"
+
+ if test -n "$GIT_MERGETOOL_VIMDIFF_DEBUG"
+ then
+ >&2 echo "$@"
+ fi
+}
+
+substring () {
+ # Return a substring of $1 containing $3 characters starting at
+ # zero-based offset $2.
+ #
+ # Examples:
+ #
+ # substring "Hello world" 0 4 --> "Hell"
+ # substring "Hello world" 3 4 --> "lo w"
+ # substring "Hello world" 3 10 --> "lo world"
+
+ STRING=$1
+ START=$2
+ LEN=$3
+
+ echo "$STRING" | cut -c$(( START + 1 ))-$(( START + $LEN ))
+}
+
+gen_cmd_aux () {
+ # Auxiliary function used from "gen_cmd()".
+ # Read that other function documentation for more details.
+
+ LAYOUT=$1
+ CMD=$2 # This is a second (hidden) argument used for recursion
+
+ debug_print
+ debug_print "LAYOUT : $LAYOUT"
+ debug_print "CMD : $CMD"
+
+ if test -z "$CMD"
+ then
+ CMD="echo" # vim "nop" operator
+ fi
+
+ start=0
+ end=${#LAYOUT}
+
+ nested=0
+ nested_min=100
+
+
+ # Step 1:
+ #
+ # Increase/decrease "start"/"end" indices respectively to get rid of
+ # outer parenthesis.
+ #
+ # Example:
+ #
+ # - BEFORE: (( LOCAL , BASE ) / MERGED )
+ # - AFTER : ( LOCAL , BASE ) / MERGED
+
+ oldIFS=$IFS
+ IFS=#
+ for c in $(echo "$LAYOUT" | sed 's:.:&#:g')
+ do
+ if test "$c" = " "
+ then
+ continue
+ fi
+
+ if test "$c" = "("
+ then
+ nested=$(( nested + 1 ))
+ continue
+ fi
+
+ if test "$c" = ")"
+ then
+ nested=$(( nested - 1 ))
+ continue
+ fi
+
+ if test "$nested" -lt "$nested_min"
+ then
+ nested_min=$nested
+ fi
+ done
+ IFS=$oldIFS
+
+ debug_print "NESTED MIN: $nested_min"
+
+ while test "$nested_min" -gt "0"
+ do
+ start=$(( start + 1 ))
+ end=$(( end - 1 ))
+
+ start_minus_one=$(( start - 1 ))
+
+ while ! test "$(substring "$LAYOUT" "$start_minus_one" 1)" = "("
+ do
+ start=$(( start + 1 ))
+ start_minus_one=$(( start_minus_one + 1 ))
+ done
+
+ while ! test "$(substring "$LAYOUT" "$end" 1)" = ")"
+ do
+ end=$(( end - 1 ))
+ done
+
+ nested_min=$(( nested_min - 1 ))
+ done
+
+ debug_print "CLEAN : $(substring "$LAYOUT" "$start" "$(( end - start ))")"
+
+
+ # Step 2:
+ #
+ # Search for all valid separators ("+", "/" or ",") which are *not*
+ # inside parenthesis. Save the index at which each of them makes the
+ # first appearance.
+
+ index_new_tab=""
+ index_horizontal_split=""
+ index_vertical_split=""
+
+ nested=0
+ i=$(( start - 1 ))
+
+ oldIFS=$IFS
+ IFS=#
+ for c in $(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:.:&#:g');
+ do
+ i=$(( i + 1 ))
+
+ if test "$c" = " "
+ then
+ continue
+ fi
+
+ if test "$c" = "("
+ then
+ nested=$(( nested + 1 ))
+ continue
+ fi
+
+ if test "$c" = ")"
+ then
+ nested=$(( nested - 1 ))
+ continue
+ fi
+
+ if test "$nested" = 0
+ then
+ current=$c
+
+ if test "$current" = "+"
+ then
+ if test -z "$index_new_tab"
+ then
+ index_new_tab=$i
+ fi
+
+ elif test "$current" = "/"
+ then
+ if test -z "$index_horizontal_split"
+ then
+ index_horizontal_split=$i
+ fi
+
+ elif test "$current" = ","
+ then
+ if test -z "$index_vertical_split"
+ then
+ index_vertical_split=$i
+ fi
+ fi
+ fi
+ done
+ IFS=$oldIFS
+
+
+ # Step 3:
+ #
+ # Process the separator with the highest order of precedence
+ # (";" has the highest precedence and "|" the lowest one).
+ #
+ # By "process" I mean recursively call this function twice: the first
+ # one with the substring at the left of the separator and the second one
+ # with the one at its right.
+
+ terminate="false"
+
+ if ! test -z "$index_new_tab"
+ then
+ before="-tabnew"
+ after="tabnext"
+ index=$index_new_tab
+ terminate="true"
+
+ elif ! test -z "$index_horizontal_split"
+ then
+ before="split"
+ after="wincmd j"
+ index=$index_horizontal_split
+ terminate="true"
+
+ elif ! test -z "$index_vertical_split"
+ then
+ before="vertical split"
+ after="wincmd l"
+ index=$index_vertical_split
+ terminate="true"
+ fi
+
+ if test "$terminate" = "true"
+ then
+ CMD="$CMD | $before"
+ CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$start" "$(( index - start ))")" "$CMD")
+ CMD="$CMD | $after"
+ CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$(( index + 1 ))" "$(( ${#LAYOUT} - index ))")" "$CMD")
+ echo "$CMD"
+ return
+ fi
+
+
+ # Step 4:
+ #
+ # If we reach this point, it means there are no separators and we just
+ # need to print the command to display the specified buffer
+
+ target=$(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:[ @();|-]::g')
+
+ if test "$target" = "LOCAL"
+ then
+ CMD="$CMD | 1b"
+
+ elif test "$target" = "BASE"
+ then
+ CMD="$CMD | 2b"
+
+ elif test "$target" = "REMOTE"
+ then
+ CMD="$CMD | 3b"
+
+ elif test "$target" = "MERGED"
+ then
+ CMD="$CMD | 4b"
+
+ else
+ CMD="$CMD | ERROR: >$target<"
+ fi
+
+ echo "$CMD"
+ return
+}
+
+
+gen_cmd () {
+ # This function returns (in global variable FINAL_CMD) the string that
+ # you can use when invoking "vim" (as shown next) to obtain a given
+ # layout:
+ #
+ # $ vim -f $FINAL_CMD "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
+ #
+ # It takes one single argument: a string containing the desired layout
+ # definition.
+ #
+ # The syntax of the "layout definitions" is explained in "Documentation/
+ # mergetools/vimdiff.txt" but you can already intuitively understand how
+ # it works by knowing that...
+ #
+ # * "+" means "a new vim tab"
+ # * "/" means "a new vim horizontal split"
+ # * "," means "a new vim vertical split"
+ #
+ # It also returns (in global variable FINAL_TARGET) the name ("LOCAL",
+ # "BASE", "REMOTE" or "MERGED") of the file that is marked with an "@",
+ # or "MERGED" if none of them is.
+ #
+ # Example:
+ #
+ # gen_cmd "@LOCAL , REMOTE"
+ # |
+ # `-> FINAL_CMD == "-c \"echo | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ # FINAL_TARGET == "LOCAL"
+
+ LAYOUT=$1
+
+
+ # Search for a "@" in one of the files identifiers ("LOCAL", "BASE",
+ # "REMOTE", "MERGED"). If not found, use "MERGE" as the default file
+ # where changes will be saved.
+
+ if echo "$LAYOUT" | grep @LOCAL >/dev/null
+ then
+ FINAL_TARGET="LOCAL"
+ elif echo "$LAYOUT" | grep @BASE >/dev/null
+ then
+ FINAL_TARGET="BASE"
+ else
+ FINAL_TARGET="MERGED"
+ fi
+
+
+ # Obtain the first part of vim "-c" option to obtain the desired layout
+
+ CMD=$(gen_cmd_aux "$LAYOUT")
+
+
+ # Adjust the just obtained script depending on whether more than one
+ # windows are visible or not
+
+ if echo "$LAYOUT" | grep ",\|/" >/dev/null
+ then
+ CMD="$CMD | tabdo windo diffthis"
+ else
+ CMD="$CMD | bufdo diffthis"
+ fi
+
+
+ # Add an extra "-c" option to move to the first tab (notice that we
+ # can't simply append the command to the previous "-c" string as
+ # explained here: https://github.com/vim/vim/issues/9076
+
+ FINAL_CMD="-c \"$CMD\" -c \"tabfirst\""
+}
+
+
+################################################################################
+## API functions (called from "git-mergetool--lib.sh")
+################################################################################
+
diff_cmd () {
"$merge_tool_path" -R -f -d \
-c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
}
+
+diff_cmd_help () {
+ TOOL=$1
+
+ case "$TOOL" in
+ nvimdiff*)
+ printf "Use Neovim"
+ ;;
+ gvimdiff*)
+ printf "Use gVim (requires a graphical session)"
+ ;;
+ vimdiff*)
+ printf "Use Vim"
+ ;;
+ esac
+
+ return 0
+}
+
+
merge_cmd () {
+ layout=$(git config mergetool.vimdiff.layout)
+
case "$1" in
*vimdiff)
- if $base_present
+ if test -z "$layout"
then
- "$merge_tool_path" -f -d -c '4wincmd w | wincmd J' \
- "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
- else
- "$merge_tool_path" -f -d -c 'wincmd l' \
- "$LOCAL" "$MERGED" "$REMOTE"
+ # Default layout when none is specified
+ layout="(LOCAL,BASE,REMOTE)/MERGED"
fi
;;
*vimdiff1)
- "$merge_tool_path" -f -d \
- -c 'echon "Resolve conflicts leftward then save. Use :cq to abort."' \
- "$LOCAL" "$REMOTE"
- ret="$?"
- if test "$ret" -eq 0
- then
- cp -- "$LOCAL" "$MERGED"
- fi
- return "$ret"
+ layout="@LOCAL,REMOTE"
;;
*vimdiff2)
- "$merge_tool_path" -f -d -c 'wincmd l' \
- "$LOCAL" "$MERGED" "$REMOTE"
+ layout="LOCAL,MERGED,REMOTE"
;;
*vimdiff3)
- if $base_present
+ layout="MERGED"
+ ;;
+ esac
+
+ gen_cmd "$layout"
+
+ debug_print ""
+ debug_print "FINAL CMD : $FINAL_CMD"
+ debug_print "FINAL TAR : $FINAL_TARGET"
+
+ if $base_present
+ then
+ eval "$merge_tool_path" \
+ -f "$FINAL_CMD" "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
+ else
+ # If there is no BASE (example: a merge conflict in a new file
+ # with the same name created in both braches which didn't exist
+ # before), close all BASE windows using vim's "quit" command
+
+ FINAL_CMD=$(echo "$FINAL_CMD" | \
+ sed -e 's:2b:quit:g' -e 's:3b:2b:g' -e 's:4b:3b:g')
+
+ eval "$merge_tool_path" \
+ -f "$FINAL_CMD" "$LOCAL" "$REMOTE" "$MERGED"
+ fi
+
+ ret="$?"
+
+ if test "$ret" -eq 0
+ then
+ case "$FINAL_TARGET" in
+ LOCAL)
+ source_path="$LOCAL"
+ ;;
+ REMOTE)
+ source_path="$REMOTE"
+ ;;
+ MERGED|*)
+ # Do nothing
+ source_path=
+ ;;
+ esac
+
+ if test -n "$source_path"
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"
+ cp "$source_path" "$MERGED"
fi
+ fi
+
+ return "$ret"
+}
+
+
+merge_cmd_help () {
+ TOOL=$1
+
+ case "$TOOL" in
+ nvimdiff*)
+ printf "Use Neovim "
+ ;;
+ gvimdiff*)
+ printf "Use gVim (requires a graphical session) "
+ ;;
+ vimdiff*)
+ printf "Use Vim "
+ ;;
+ esac
+
+ case "$TOOL" in
+ *1)
+ echo "with a 2 panes layout (LOCAL and REMOTE)"
+ ;;
+ *2)
+ echo "with a 3 panes layout (LOCAL, MERGED and REMOTE)"
+ ;;
+ *3)
+ echo "where only the MERGED file is shown"
+ ;;
+ *)
+ echo "with a custom layout (see \`git help mergetool\`'s \`BACKEND SPECIFIC HINTS\` section)"
;;
esac
+
+ return 0
}
-translate_merge_tool_path() {
+
+translate_merge_tool_path () {
case "$1" in
nvimdiff*)
echo nvim
esac
}
+
exit_code_trustable () {
true
}
+
list_tool_variants () {
- for prefix in '' g n; do
- for suffix in '' 1 2 3; do
- echo "${prefix}vimdiff${suffix}"
+ if test "$TOOL_MODE" = "diff"
+ then
+ for prefix in '' g n
+ do
+ echo "${prefix}vimdiff"
+ done
+ else
+ for prefix in '' g n
+ do
+ for suffix in '' 1 2 3
+ do
+ echo "${prefix}vimdiff${suffix}"
+ done
done
+ fi
+}
+
+
+################################################################################
+## Unit tests (called from scripts inside the "t" folder)
+################################################################################
+
+run_unit_tests () {
+ # Function to make sure that we don't break anything when modifying this
+ # script.
+
+ NUMBER_OF_TEST_CASES=16
+
+ TEST_CASE_01="(LOCAL,BASE,REMOTE)/MERGED" # default behaviour
+ TEST_CASE_02="@LOCAL,REMOTE" # when using vimdiff1
+ TEST_CASE_03="LOCAL,MERGED,REMOTE" # when using vimdiff2
+ TEST_CASE_04="MERGED" # when using vimdiff3
+ TEST_CASE_05="LOCAL/MERGED/REMOTE"
+ TEST_CASE_06="(LOCAL/REMOTE),MERGED"
+ TEST_CASE_07="MERGED,(LOCAL/REMOTE)"
+ TEST_CASE_08="(LOCAL,REMOTE)/MERGED"
+ TEST_CASE_09="MERGED/(LOCAL,REMOTE)"
+ TEST_CASE_10="(LOCAL/BASE/REMOTE),MERGED"
+ TEST_CASE_11="(LOCAL,BASE,REMOTE)/MERGED+BASE,LOCAL+BASE,REMOTE+(LOCAL/BASE/REMOTE),MERGED"
+ TEST_CASE_12="((LOCAL,REMOTE)/BASE),MERGED"
+ TEST_CASE_13="((LOCAL,REMOTE)/BASE),((LOCAL/REMOTE),MERGED)"
+ TEST_CASE_14="BASE,REMOTE+BASE,LOCAL"
+ TEST_CASE_15=" (( (LOCAL , BASE , REMOTE) / MERGED)) +(BASE) , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) , MERGED ) "
+ TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED"
+
+ EXPECTED_CMD_01="-c \"echo | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_02="-c \"echo | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_03="-c \"echo | vertical split | 1b | wincmd l | vertical split | 4b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_04="-c \"echo | 4b | bufdo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_05="-c \"echo | split | 1b | wincmd j | split | 4b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_06="-c \"echo | vertical split | split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_07="-c \"echo | vertical split | 4b | wincmd l | split | 1b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_08="-c \"echo | split | vertical split | 1b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_09="-c \"echo | split | 4b | wincmd j | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_10="-c \"echo | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_11="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_12="-c \"echo | vertical split | split | vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_13="-c \"echo | vertical split | split | vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | vertical split | split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_14="-c \"echo | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | 2b | wincmd l | 1b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_15="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_16="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+
+ EXPECTED_TARGET_01="MERGED"
+ EXPECTED_TARGET_02="LOCAL"
+ EXPECTED_TARGET_03="MERGED"
+ EXPECTED_TARGET_04="MERGED"
+ EXPECTED_TARGET_05="MERGED"
+ EXPECTED_TARGET_06="MERGED"
+ EXPECTED_TARGET_07="MERGED"
+ EXPECTED_TARGET_08="MERGED"
+ EXPECTED_TARGET_09="MERGED"
+ EXPECTED_TARGET_10="MERGED"
+ EXPECTED_TARGET_11="MERGED"
+ EXPECTED_TARGET_12="MERGED"
+ EXPECTED_TARGET_13="MERGED"
+ EXPECTED_TARGET_14="MERGED"
+ EXPECTED_TARGET_15="MERGED"
+ EXPECTED_TARGET_16="MERGED"
+
+ at_least_one_ko="false"
+
+ for i in $(seq -w 1 99)
+ do
+ if test "$i" -gt $NUMBER_OF_TEST_CASES
+ then
+ break
+ fi
+
+ gen_cmd "$(eval echo \${TEST_CASE_"$i"})"
+
+ if test "$FINAL_CMD" = "$(eval echo \${EXPECTED_CMD_"$i"})" \
+ && test "$FINAL_TARGET" = "$(eval echo \${EXPECTED_TARGET_"$i"})"
+ then
+ printf "Test Case #%02d: OK\n" "$(echo "$i" | sed 's/^0*//')"
+ else
+ printf "Test Case #%02d: KO !!!!\n" "$(echo "$i" | sed 's/^0*//')"
+ echo " FINAL_CMD : $FINAL_CMD"
+ echo " FINAL_CMD (expected) : $(eval echo \${EXPECTED_CMD_"$i"})"
+ echo " FINAL_TARGET : $FINAL_TARGET"
+ echo " FINAL_TARGET (expected): $(eval echo \${EXPECTED_TARGET_"$i"})"
+ at_least_one_ko="true"
+ fi
done
+
+ if test "$at_least_one_ko" = "true"
+ then
+ return 255
+ else
+ return 0
+ fi
}
return 0
}
+diff_cmd_help () {
+ echo "Use WinMerge (requires a graphical session)"
+}
+
merge_cmd () {
# mergetool.winmerge.trustExitCode is implicitly false.
# touch $BACKUP so that we can check_unchanged.
translate_merge_tool_path() {
mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"
}
+
+merge_cmd_help () {
+ echo "Use WinMerge (requires a graphical session)"
+}
fi
}
+diff_cmd_help () {
+ echo "Use xxdiff (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
--merged-file "$MERGED" "$LOCAL" "$REMOTE"
fi
}
+
+merge_cmd_help () {
+ echo "Use xxdiff (requires a graphical session)"
+}
static struct multi_pack_index *lookup_multi_pack_index(struct repository *r,
const char *object_dir)
{
+ struct multi_pack_index *result = NULL;
struct multi_pack_index *cur;
+ char *obj_dir_real = real_pathdup(object_dir, 1);
+ struct strbuf cur_path_real = STRBUF_INIT;
/* Ensure the given object_dir is local, or a known alternate. */
- find_odb(r, object_dir);
+ find_odb(r, obj_dir_real);
for (cur = get_multi_pack_index(r); cur; cur = cur->next) {
- if (!strcmp(object_dir, cur->object_dir))
- return cur;
+ strbuf_realpath(&cur_path_real, cur->object_dir, 1);
+ if (!strcmp(obj_dir_real, cur_path_real.buf)) {
+ result = cur;
+ goto cleanup;
+ }
}
- return NULL;
+cleanup:
+ free(obj_dir_real);
+ strbuf_release(&cur_path_real);
+ return result;
}
static int write_midx_internal(const char *object_dir,
die(_("loose object %s (stored in %s) is corrupt"),
oid_to_hex(repl), path);
- if ((p = has_packed_and_bad(r, repl)) != NULL)
+ if ((p = has_packed_and_bad(r, repl)))
die(_("packed object %s (stored in %s) is corrupt"),
oid_to_hex(repl), p->pack_name);
obj_read_unlock();
goto out;
}
- if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
- NULL) < 0) {
+ switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
+ NULL)) {
+ case ULHR_OK:
+ break;
+ case ULHR_BAD:
+ case ULHR_TOO_LONG:
error(_("unable to unpack header of %s"), path);
goto out;
}
pos = -pos - 1;
if (pos < istate->cache_nr) {
ce = istate->cache[pos];
- if (ce_namelen(ce) == namelen &&
+ if (!S_ISSPARSEDIR(ce->ce_mode) &&
+ ce_namelen(ce) == namelen &&
!memcmp(ce->name, filename, namelen))
die(_("path '%s' is in the index, but not at stage %d\n"
"hint: Did you mean ':%d:%s'?"),
pos = -pos - 1;
if (pos < istate->cache_nr) {
ce = istate->cache[pos];
- if (ce_namelen(ce) == fullname.len &&
+ if (!S_ISSPARSEDIR(ce->ce_mode) &&
+ ce_namelen(ce) == fullname.len &&
!memcmp(ce->name, fullname.buf, fullname.len))
die(_("path '%s' is in the index, but not '%s'\n"
"hint: Did you mean ':%d:%s' aka ':%d:./%s'?"),
rel);
}
+static int reject_tree_in_index(struct repository *repo,
+ int only_to_die,
+ const struct cache_entry *ce,
+ int stage,
+ const char *prefix,
+ const char *cp)
+{
+ if (!S_ISSPARSEDIR(ce->ce_mode))
+ return 0;
+ if (only_to_die)
+ diagnose_invalid_index_path(repo, stage, prefix, cp);
+ return -1;
+}
+
static enum get_oid_result get_oid_with_context_1(struct repository *repo,
const char *name,
unsigned flags,
memcmp(ce->name, cp, namelen))
break;
if (ce_stage(ce) == stage) {
+ free(new_path);
+ if (reject_tree_in_index(repo, only_to_die, ce,
+ stage, prefix, cp))
+ return -1;
oidcpy(oid, &ce->oid);
oc->mode = ce->ce_mode;
- free(new_path);
return 0;
}
pos++;
* These functions can be removed once all callers have migrated to
* has_object() and/or oid_object_info_extended().
*/
-#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
-#define has_sha1_file_with_flags(sha1, flags) repo_has_sha1_file_with_flags(the_repository, sha1, flags)
-#define has_sha1_file(sha1) repo_has_sha1_file(the_repository, sha1)
-#endif
int repo_has_object_file(struct repository *r, const struct object_id *oid);
int repo_has_object_file_with_flags(struct repository *r,
const struct object_id *oid, int flags);
struct ewah_bitmap *parent;
struct ewah_bitmap *composed;
- if (st->xor == NULL)
+ if (!st->xor)
return st->root;
composed = ewah_pool_new();
if (xor_offset > 0) {
xor_bitmap = recent_bitmaps[(i - xor_offset) % MAX_XOR_OFFSET];
- if (xor_bitmap == NULL)
+ if (!xor_bitmap)
return error("Invalid XOR offset in bitmap pack index");
}
if (!or_with)
return 0;
- if (*base == NULL)
+ if (!*base)
*base = ewah_to_bitmap(or_with);
else
bitmap_or_ewah(*base, or_with);
* Best case scenario: We found bitmaps for all the roots,
* so the resulting `or` bitmap has the full reachability analysis
*/
- if (not_mapped == NULL)
+ if (!not_mapped)
return base;
roots = not_mapped;
struct include_data incdata;
struct bitmap_show_data show_data;
- if (base == NULL)
+ if (!base)
base = bitmap_new();
incdata.bitmap_git = bitmap_git;
reset_revision_walk();
revs->ignore_missing_links = 0;
- if (haves_bitmap == NULL)
+ if (!haves_bitmap)
BUG("failed to perform bitmap walk");
}
result = ewah_to_bitmap(bm);
}
- if (result == NULL)
+ if (!result)
die("Commit %s doesn't have an indexed bitmap", oid_to_hex(&root->oid));
revs->tag_objects = 1;
if (idx_size < 4 * 256 + hashsz + hashsz)
return error("index file %s is too small", path);
- if (idx_map == NULL)
+ if (!idx_map)
return error("empty data");
if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
struct strbuf user_path = STRBUF_INIT;
const char *to_copy = path;
- if (path == NULL)
+ if (!path)
goto return_null;
if (skip_prefix(path, "%(prefix)/", &path))
{
int i, j;
- if (queue->compare != NULL)
+ if (queue->compare)
BUG("prio_queue_reverse() on non-LIFO queue");
for (i = 0; i < (j = (queue->nr - 1) - i); i++)
swap(queue, i, j);
struct promisor_remote *r,
struct promisor_remote *previous)
{
- if (r->next == NULL)
+ if (!r->next)
return;
if (previous)
return consumed;
}
+static void set_new_index_sparsity(struct index_state *istate)
+{
+ /*
+ * If the index's repo exists, mark it sparse according to
+ * repo settings.
+ */
+ if (istate->repo) {
+ prepare_repo_settings(istate->repo);
+ if (!istate->repo->settings.command_requires_full_index &&
+ is_sparse_index_allowed(istate, 0))
+ istate->sparse_index = 1;
+ }
+}
+
/* remember to discard_cache() before reading a different cache! */
int do_read_index(struct index_state *istate, const char *path, int must_exist)
{
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY);
if (fd < 0) {
- if (!must_exist && errno == ENOENT)
+ if (!must_exist && errno == ENOENT) {
+ set_new_index_sparsity(istate);
return 0;
+ }
die_errno(_("%s: index file open failed"), path);
}
* ":" means no format is specified, and use the default.
*/
formatp = strchr(atomname, ':');
- if (formatp != NULL) {
+ if (formatp) {
formatp++;
parse_date_format(formatp, &date_mode);
}
int i;
for (i = 0; i < used_atom_cnt; i++) {
struct atom_value *v = &val[i];
- if (v->s == NULL)
+ if (!v->s)
v->s = xstrdup("");
}
}
while (remaining-- > 0) {
char *p = strrchr(start, '/');
- if (p == NULL) {
+ if (!p) {
free((char *)to_free);
return xstrdup("");
} else
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid))
- BUG("create called without valid new_oid");
+ if (!new_oid || is_null_oid(new_oid)) {
+ strbuf_addf(err, "'%s' has a null OID", refname);
+ return 1;
+ }
return ref_transaction_update(transaction, refname, new_oid,
null_oid(), flags, msg, err);
}
r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries),
ref_entry_cmp_sslice);
- if (r == NULL)
+ if (!r)
return -1;
return r - dir->entries;
static void strbuf_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
static void malloc_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
static void file_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
DIR *dir = opendir(dirname);
int len = 0;
struct dirent *d;
- if (dir == NULL)
+ if (!dir)
return 0;
while ((d = readdir(dir))) {
int insert)
{
int res;
- if (*rootp == NULL) {
+ if (!*rootp) {
if (!insert) {
return NULL;
} else {
void tree_free(struct tree_node *t)
{
- if (t == NULL) {
+ if (!t) {
return;
}
if (t->left) {
struct tree_node *node = tree_search(&want, &w->obj_index_tree,
&obj_index_tree_node_compare, 0);
struct obj_index_tree_node *key = NULL;
- if (node == NULL) {
+ if (!node) {
struct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;
key = reftable_malloc(sizeof(struct obj_index_tree_node));
*key = empty;
strbuf_reset(&w->last_key);
strbuf_addbuf(&w->last_key, &key);
- if (w->block_writer == NULL) {
+ if (!w->block_writer) {
writer_reinit_block_writer(w, reftable_record_type(rec));
}
};
int err = 0;
- if (ref->refname == NULL)
+ if (!ref->refname)
return REFTABLE_API_ERROR;
if (ref->update_index < w->min_update_index ||
ref->update_index > w->max_update_index)
if (log->value_type == REFTABLE_LOG_DELETION)
return reftable_writer_add_log_verbatim(w, log);
- if (log->refname == NULL)
+ if (!log->refname)
return REFTABLE_API_ERROR;
input_log_message = log->value.update.message;
uint8_t typ = 0;
int err = 0;
- if (w->block_writer == NULL)
+ if (!w->block_writer)
return 0;
typ = block_writer_type(w->block_writer);
static int writer_flush_block(struct reftable_writer *w)
{
- if (w->block_writer == NULL)
+ if (!w->block_writer)
return 0;
if (w->block_writer->entries == 0)
return 0;
else if (conflict_type == RESOLVED) {
struct string_list_item *it;
it = string_list_lookup(merge_rr, (const char *)e->name);
- if (it != NULL) {
+ if (it) {
free_rerere_id(it);
it->util = RERERE_RESOLVED;
}
if (revs->min_age != -1 && (commit->date > revs->min_age) &&
!revs->line_level_traverse)
continue;
+ if (revs->max_age_as_filter != -1 &&
+ (commit->date < revs->max_age_as_filter) && !revs->line_level_traverse)
+ continue;
date = commit->date;
p = &commit_list_insert(commit, p)->next;
revs->dense = 1;
revs->prefix = prefix;
revs->max_age = -1;
+ revs->max_age_as_filter = -1;
revs->min_age = -1;
revs->skip_count = -1;
revs->max_count = -1;
} else if ((argcount = parse_long_opt("since", argv, &optarg))) {
revs->max_age = approxidate(optarg);
return argcount;
+ } else if ((argcount = parse_long_opt("since-as-filter", argv, &optarg))) {
+ revs->max_age_as_filter = approxidate(optarg);
+ return argcount;
} else if ((argcount = parse_long_opt("after", argv, &optarg))) {
revs->max_age = approxidate(optarg);
return argcount;
}
strvec_clear(&prune_data);
- if (revs->def == NULL)
+ if (!revs->def)
revs->def = opt ? opt->def : NULL;
if (opt && opt->tweak)
opt->tweak(revs, opt);
return rewrite_one_ok;
if (!p->parents)
return rewrite_one_noparents;
- if ((p = one_relevant_parent(revs, p->parents)) == NULL)
+ if (!(p = one_relevant_parent(revs, p->parents)))
return rewrite_one_ok;
*pp = p;
}
if (revs->min_age != -1 &&
comparison_date(revs, commit) > revs->min_age)
return commit_ignore;
+ if (revs->max_age_as_filter != -1 &&
+ comparison_date(revs, commit) < revs->max_age_as_filter)
+ return commit_ignore;
if (revs->min_parents || (revs->max_parents >= 0)) {
int n = commit_list_count(commit->parents);
if ((n < revs->min_parents) ||
int skip_count;
int max_count;
timestamp_t max_age;
+ timestamp_t max_age_as_filter;
timestamp_t min_age;
int min_parents;
int max_parents;
unsigned clean_on_exit:1;
unsigned wait_after_clean:1;
void (*clean_on_exit_handler)(struct child_process *process);
- void *clean_on_exit_handler_cbdata;
};
#define CHILD_PROCESS_INIT { \
get_commit_format(format.buf, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
- rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
refs = get_main_ref_store(the_repository);
#include "config.h"
#include "pkt-line.h"
#include "version.h"
-#include "strvec.h"
#include "ls-refs.h"
#include "protocol-caps.h"
#include "serve.h"
int i;
char *endptr;
- if (value == NULL)
+ if (!value)
return PERM_GROUP;
if (!strcmp(value, "umask"))
/* Substitute the variable's value from the environment. */
const char *env_value = getenv (buffer);
- if (env_value != NULL)
+ if (env_value)
fputs (env_value, stdout);
}
else
else
c->object.flags |= SEEN;
- if (*refs == NULL)
+ if (!*refs)
*refs = bitmap;
else {
memcpy(tmp, *refs, bitmap_size);
return 0;
}
-static int is_sparse_index_allowed(struct index_state *istate, int flags)
+int is_sparse_index_allowed(struct index_state *istate, int flags)
{
if (!core_apply_sparse_checkout || !core_sparse_checkout_cone)
return 0;
struct index_state;
#define SPARSE_INDEX_MEMORY_ONLY (1 << 0)
+int is_sparse_index_allowed(struct index_state *istate, int flags);
int convert_to_sparse(struct index_state *istate, int flags);
void ensure_correct_sparsity(struct index_state *istate);
void clear_skip_worktree_from_present_files(struct index_state *istate);
const char *path;
const char *name;
const char *url;
- int fetch_recurse;
+ enum submodule_recurse_mode fetch_recurse;
const char *ignore;
const char *branch;
struct submodule_update_strategy update_strategy;
struct string_list;
struct strbuf;
-enum {
+enum submodule_recurse_mode {
RECURSE_SUBMODULES_ONLY = -5,
RECURSE_SUBMODULES_CHECK = -4,
RECURSE_SUBMODULES_ERROR = -3,
' "$@" <actual
}
+get_progress_result () {
+ tr '\015' '\012' | tail -n 1
+}
+
test_expect_success 'setup A lines' '
echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
test_expect_success 'blame -L ,^/RE/' '
test_must_fail $PROG -L1,^/99/ file
'
+
+test_expect_success 'blame progress on a full file' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (10/10), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'blame progress on a single range' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (4/4), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress -L 3,6 hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'blame progress on multiple ranges' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (7/7), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress -L 3,6 -L 8,10 hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
p4_add_user () {
name=$1 &&
+ fullname="${2:-Dr. $1}"
p4 user -f -i <<-EOF
User: $name
Email: $name@example.com
- FullName: Dr. $name
+ FullName: $fullname
EOF
}
}
test_perf_on_all git status
+test_perf_on_all 'git stash && git stash pop'
+test_perf_on_all 'echo >>new && git stash -u && git stash pop'
test_perf_on_all git add -A
test_perf_on_all git add .
test_perf_on_all git commit -a -m A
do
test_expect_success "'$cmd' section spacing" '
test_section_spacing_trailer git help <<-\EOF &&
- usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
+ usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
These are common Git commands used in various situations:
check_warning () {
case "$1" in
- LF_CRLF) echo "warning: LF will be replaced by CRLF" >"$2".expect ;;
- CRLF_LF) echo "warning: CRLF will be replaced by LF" >"$2".expect ;;
- '') >"$2".expect ;;
+ LF_CRLF) echo "LF will be replaced by CRLF" >"$2".expect ;;
+ CRLF_LF) echo "CRLF will be replaced by LF" >"$2".expect ;;
+ '') >"$2".expect ;;
*) echo >&2 "Illegal 1": "$1" ; return false ;;
esac
- grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" | uniq >"$2".actual
+ sed -e "s/^.* \([^ ]* will be replaced by [^ ]*\) .*$/\1/" "$2" | uniq >"$2".actual
test_cmp "$2".expect "$2".actual
}
expect_rejected_dir () {
test_must_fail git status 2>err &&
- grep "safe.directory" err
+ grep "unsafe repository" err
}
test_expect_success 'safe.directory is not set' '
expect_rejected_dir
'
+test_expect_success 'ignoring safe.directory on the command line' '
+ test_must_fail git -c safe.directory="$(pwd)" status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in the environment' '
+ test_must_fail env GIT_CONFIG_COUNT=1 \
+ GIT_CONFIG_KEY_0="safe.directory" \
+ GIT_CONFIG_VALUE_0="$(pwd)" \
+ git status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in GIT_CONFIG_PARAMETERS' '
+ test_must_fail env \
+ GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \
+ git status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in repo config' '
+ (
+ unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
+ git config safe.directory "$(pwd)"
+ ) &&
+ expect_rejected_dir
+'
+
test_expect_success 'safe.directory does not match' '
git config --global safe.directory bogus &&
expect_rejected_dir
./git rev-parse
'
-test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX works' '
+test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX works' '
mkdir -p pretend/bin pretend/libexec/git-core &&
echo "echo HERE" | write_script pretend/libexec/git-core/git-here &&
cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
echo HERE >expect &&
test_cmp expect actual'
-test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
+test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
mkdir -p pretend/bin &&
cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
git config yes.path "%(prefix)/yes" &&
# Setup and create the empty blob and its path
empty_path=$(git rev-parse --git-path objects/$(test_oid_to_path "$EMPTY_BLOB")) &&
- git hash-object -w --stdin </dev/null &&
+ empty_blob=$(git hash-object -w --stdin </dev/null) &&
# Create another blob and its path
echo other >other.blob &&
# content out as-is. Try to make it zlib-invalid.
mv -f other.blob "$empty_path" &&
test_must_fail git fsck 2>err.fsck &&
- grep "^error: inflate: data stream error (" err.fsck
+ cat >expect <<-EOF &&
+ error: inflate: data stream error (incorrect header check)
+ error: unable to unpack header of ./$empty_path
+ error: $empty_blob: object corrupt or missing: ./$empty_path
+ EOF
+ grep "^error: " err.fsck >actual &&
+ test_cmp expect actual
)
'
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt result &&
- test -f init.t &&
- test -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
read_tree_u_must_succeed --no-sparse-checkout -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt result &&
- test -f init.t &&
- test -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
S subsub/added
EOF
test_cmp expected.swt result &&
- ! test -f init.t &&
- ! test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_missing sub/added
'
test_expect_success 'match directories with trailing slash' '
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t > result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'match directories without trailing slash' '
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'match directories with negated patterns' '
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-negation result &&
- test ! -f init.t &&
- test ! -f sub/added &&
- test -f sub/addedtoo
+ test_path_is_missing init.t &&
+ test_path_is_missing sub/added &&
+ test_path_is_file sub/addedtoo
'
test_expect_success 'match directories with negated patterns (2)' '
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-negation2 result &&
- test -f init.t &&
- test -f sub/added &&
- test ! -f sub/addedtoo
+ test_path_is_file init.t &&
+ test_path_is_file sub/added &&
+ test_path_is_missing sub/addedtoo
'
test_expect_success 'match directory pattern' '
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'checkout area changes' '
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-nosub result &&
- test -f init.t &&
- test ! -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_missing sub/added
'
test_expect_success 'read-tree updates worktree, absent case' '
echo sub/added >.git/info/sparse-checkout &&
git checkout -f top &&
read_tree_u_must_succeed -m -u HEAD^ &&
- test ! -f init.t
+ test_path_is_missing init.t
'
test_expect_success 'read-tree will not throw away dirty changes, non-sparse' '
echo init.t >.git/info/sparse-checkout &&
git checkout -f removed &&
read_tree_u_must_succeed -u -m HEAD^ &&
- test ! -f sub/added
+ test_path_is_missing sub/added
'
test_expect_success 'read-tree adds to worktree, dirty case' '
echo init.t >.git/info/sparse-checkout &&
git checkout removed &&
git ls-files sub/added >result &&
- test ! -f sub/added &&
+ test_path_is_missing sub/added &&
test_must_be_empty result
'
test_all_match test_must_fail git cherry-pick to-cherry-pick
'
+test_expect_success 'stash' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+
+ # Stash a sparse directory (folder1)
+ test_all_match git checkout -b test-branch rename-base &&
+ test_all_match git reset --soft rename-out-to-out &&
+ test_all_match git stash &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Apply the sparse directory stash without reinstating the index
+ test_all_match git stash apply -q &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Reset to state where stash can be applied
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match git reset --hard rename-out-to-out &&
+
+ # Apply the sparse directory stash *with* reinstating the index
+ test_all_match git stash apply --index -q &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Reset to state where we will get a conflict applying the stash
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match git reset --hard update-folder1 &&
+
+ # Apply the sparse directory stash with conflicts
+ test_all_match test_must_fail git stash apply --index -q &&
+ test_all_match test_must_fail git stash apply -q &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Reset to base branch
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match git reset --hard base &&
+
+ # Stash & unstash an untracked file outside of the sparse checkout
+ # definition.
+ run_on_sparse mkdir -p folder1 &&
+ run_on_all ../edit-contents folder1/new &&
+ test_all_match git stash -u &&
+ test_all_match git status --porcelain=v2 &&
+
+ test_all_match git stash pop -q &&
+ test_all_match git status --porcelain=v2
+'
+
test_expect_success 'checkout-index inside sparse definition' '
init_repos &&
test_sparse_match test_path_is_dir folder1
'
+for builtin in show rev-parse
+do
+ test_expect_success "$builtin (cached blobs/trees)" "
+ init_repos &&
+
+ test_all_match git $builtin :a &&
+ test_all_match git $builtin :deep/a &&
+ test_sparse_match git $builtin :folder1/a &&
+
+ # The error message differs depending on whether
+ # the directory exists in the worktree.
+ test_all_match test_must_fail git $builtin :deep/ &&
+ test_must_fail git -C full-checkout $builtin :folder1/ &&
+ test_sparse_match test_must_fail git $builtin :folder1/ &&
+
+ # Change the sparse cone for an extra case:
+ run_on_sparse git sparse-checkout set deep/deeper1 &&
+
+ # deep/deeper2 is a sparse directory in the sparse index.
+ test_sparse_match test_must_fail git $builtin :deep/deeper2/ &&
+
+ # deep/deeper2/deepest is not in the sparse index, but
+ # will trigger an index expansion.
+ test_sparse_match test_must_fail git $builtin :deep/deeper2/deepest/
+ "
+done
+
test_expect_success 'submodule handling' '
init_repos &&
ensure_not_expanded () {
rm -f trace2.txt &&
- echo >>sparse-index/untracked.txt &&
+ if test -z "$WITHOUT_UNTRACKED_TXT"
+ then
+ echo >>sparse-index/untracked.txt
+ fi &&
if test "$1" = "!"
then
)
'
+test_expect_success 'sparse-index is not expanded: stash' '
+ init_repos &&
+
+ echo >>sparse-index/a &&
+ ensure_not_expanded stash &&
+ ensure_not_expanded stash list &&
+ ensure_not_expanded stash show stash@{0} &&
+ ensure_not_expanded stash apply stash@{0} &&
+ ensure_not_expanded stash drop stash@{0} &&
+
+ echo >>sparse-index/deep/new &&
+ ensure_not_expanded stash -u &&
+ (
+ WITHOUT_UNTRACKED_TXT=1 &&
+ ensure_not_expanded stash pop
+ ) &&
+
+ ensure_not_expanded stash create &&
+ oid=$(git -C sparse-index stash create) &&
+ ensure_not_expanded stash store -m "test" $oid &&
+ ensure_not_expanded reset --hard &&
+ ensure_not_expanded stash pop
+'
+
test_expect_success 'sparse index is not expanded: diff' '
init_repos &&
ensure_not_expanded diff --cached
'
+test_expect_success 'sparse index is not expanded: show and rev-parse' '
+ init_repos &&
+
+ ensure_not_expanded show :a &&
+ ensure_not_expanded show :deep/a &&
+ ensure_not_expanded rev-parse :a &&
+ ensure_not_expanded rev-parse :deep/a
+'
+
test_expect_success 'sparse index is not expanded: update-index' '
init_repos &&
# no "-d" here, so we end up with duplicates
git repack &&
# now corrupt the loose copy
- file=$(sha1_file "$(git rev-parse HEAD)") &&
+ oid="$(git rev-parse HEAD)" &&
+ file=$(sha1_file "$oid") &&
rm "$file" &&
echo broken >"$file" &&
- test_must_fail git fsck
+ test_must_fail git fsck 2>err &&
+
+ cat >expect <<-EOF &&
+ error: inflate: data stream error (incorrect header check)
+ error: unable to unpack header of $file
+ error: $oid: object corrupt or missing: $file
+ EOF
+ grep "^error: " err >actual &&
+ test_cmp expect actual
)
'
test_description='Intent to add'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'intent to add' '
test_cmp actual expect
'
+# incompatible options
+while read combo
+do
+ test_expect_success "show-branch $combo (should fail)" '
+ test_must_fail git show-branch $combo 2>error &&
+ grep -e "cannot be used together" -e "usage:" error
+ '
+done <<\EOF
+--all --reflog
+--merge-base --reflog
+--list --merge-base
+--reflog --current
+EOF
+
test_done
test_cmp expect actual
'
+test_expect_success 'rebase --keep-base main topic from main' '
+ git checkout main &&
+ git branch -f topic G &&
+
+ git rebase --keep-base main topic &&
+ git rev-parse C >base.expect &&
+ git merge-base main HEAD >base.actual &&
+ test_cmp base.expect base.actual &&
+
+ git rev-parse HEAD~2 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase --keep-base main from side' '
git reset --hard &&
git checkout side &&
test_cmp expect actual
'
+test_expect_success 'rebase -i --keep-base main topic from main' '
+ git checkout main &&
+ git branch -f topic G &&
+
+ set_fake_editor &&
+ EXPECT_COUNT=2 git rebase -i --keep-base main topic &&
+ git rev-parse C >base.expect &&
+ git merge-base main HEAD >base.actual &&
+ test_cmp base.expect base.actual &&
+
+ git rev-parse HEAD~2 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase -i --keep-base main from side' '
git reset --hard &&
git checkout side &&
git checkout rename2 &&
git cherry-pick added &&
- test $(git rev-parse HEAD^) = $(git rev-parse rename2) &&
- test -f opos &&
+ test_cmp_rev rename2 HEAD^ &&
grep "Add extra line at the end" opos &&
git reflog -1 | grep cherry-pick
git checkout rename1 &&
git revert added &&
- test $(git rev-parse HEAD^) = $(git rev-parse rename1) &&
- test -f spoo &&
- ! grep "Add extra line at the end" spoo &&
+ test_cmp_rev rename1 HEAD^ &&
+ test_path_is_file spoo &&
+ test_cmp_rev initial:oops HEAD:spoo &&
git reflog -1 | grep revert
'
'
test_expect_success 'GIT_EXTERNAL_DIFF generates pretty paths' '
+ test_when_finished "git rm -f file.ext" &&
touch file.ext &&
git add file.ext &&
echo with extension > file.ext &&
cat >expect <<-EOF &&
- file.ext file $(git rev-parse --verify HEAD:file) 100644 file.ext $(test_oid zero) 100644
+ file.ext
EOF
GIT_EXTERNAL_DIFF=echo git diff file.ext >out &&
- cut -d" " -f1,3- <out >actual &&
- git update-index --force-remove file.ext &&
- rm file.ext
+ basename $(cut -d" " -f2 <out) >actual &&
+ test_cmp expect actual
'
echo "#!$SHELL_PATH" >fake-diff.sh
git tag -s -m signed_tag_msg signed_tag_fail &&
git checkout plain-fail &&
git merge --no-ff -m msg signed_tag_fail &&
- TMPDIR="$(pwd)/bogus" git log --show-signature -n1 plain-fail >actual &&
- grep "^merged tag" actual &&
- grep "^No signature" actual &&
- ! grep "^gpg: Signature made" actual
+ if ! test_have_prereq VALGRIND
+ then
+ TMPDIR="$(pwd)/bogus" git log --show-signature -n1 plain-fail >actual &&
+ grep "^merged tag" actual &&
+ grep "^No signature" actual &&
+ ! grep "^gpg: Signature made" actual
+ fi
'
test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
--- /dev/null
+#!/bin/sh
+
+test_description='git log with filter options limiting the output'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test' '
+ git init &&
+ echo a >file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2021-02-01 00:00" git commit -m init &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2022-02-01 00:00" git commit -m first &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2021-03-01 00:00" git commit -m second &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2022-03-01 00:00" git commit -m third
+'
+
+test_expect_success 'git log --since-as-filter=...' '
+ git log --since-as-filter="2022-01-01" --format=%s >actual &&
+ cat >expect <<-\EOF &&
+ third
+ first
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'git log --children --since-as-filter=...' '
+ git log --children --since-as-filter="2022-01-01" --format=%s >actual &&
+ cat >expect <<-\EOF &&
+ third
+ first
+ EOF
+ test_cmp expect actual
+'
+
+test_done
test_path_is_file super/sub/merge_strategy_4.t
'
+test_expect_success "fetch.recurseSubmodules option triggers recursive fetch (but not recursive update)" '
+ test_commit -C child merge_strategy_5 &&
+ # Omit the parent commit, otherwise this passes with the
+ # default "pull" behavior.
+
+ git -C super -c fetch.recursesubmodules=true pull --no-rebase &&
+ # Check that the submodule commit was fetched
+ sub_oid=$(git -C child rev-parse HEAD) &&
+ git -C super/sub cat-file -e $sub_oid &&
+ # Check that the submodule worktree did not update
+ ! test_path_is_file super/sub/merge_strategy_5.t
+'
+
+test_expect_success "fetch.recurseSubmodules takes precedence over submodule.recurse" '
+ test_commit -C child merge_strategy_6 &&
+ # Omit the parent commit, otherwise this passes with the
+ # default "pull" behavior.
+
+ git -C super -c submodule.recurse=false -c fetch.recursesubmodules=true pull --no-rebase &&
+ # Check that the submodule commit was fetched
+ sub_oid=$(git -C child rev-parse HEAD) &&
+ git -C super/sub cat-file -e $sub_oid &&
+ # Check that the submodule worktree did not update
+ ! test_path_is_file super/sub/merge_strategy_6.t
+'
+
test_expect_success 'pull --rebase --recurse-submodules (remote superproject submodule changes, local submodule changes)' '
# This tests the following scenario :
# - local submodule has new commits
test_must_fail git clone --bare -u false a should_not_work.git
'
+test_expect_success 'local clone from repo with corrupt refs fails gracefully' '
+ git init corrupt &&
+ test_commit -C corrupt one &&
+ echo a >corrupt/.git/refs/heads/topic &&
+
+ test_must_fail git clone corrupt working 2>err &&
+ grep "has a null OID" err
+'
+
test_done
git bisect visualize -p -- "-hello 2"
'
+test_expect_success 'bisect state output with multiple good commits' '
+ git bisect reset &&
+ git bisect start >output &&
+ grep "waiting for both good and bad commits" output &&
+ git bisect log >output &&
+ grep "waiting for both good and bad commits" output &&
+ git bisect good "$HASH1" >output &&
+ grep "waiting for bad commit, 1 good commit known" output &&
+ git bisect log >output &&
+ grep "waiting for bad commit, 1 good commit known" output &&
+ git bisect good "$HASH2" >output &&
+ grep "waiting for bad commit, 2 good commits known" output &&
+ git bisect log >output &&
+ grep "waiting for bad commit, 2 good commits known" output
+'
+
+test_expect_success 'bisect state output with bad commit' '
+ git bisect reset &&
+ git bisect start >output &&
+ grep "waiting for both good and bad commits" output &&
+ git bisect log >output &&
+ grep "waiting for both good and bad commits" output &&
+ git bisect bad "$HASH4" >output &&
+ grep -F "waiting for good commit(s), bad commit known" output &&
+ git bisect log >output &&
+ grep -F "waiting for good commit(s), bad commit known" output
+'
+
test_done
test_description='skip-worktree bit test'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
cat >expect.full <<EOF
test_cmp ../dump.expect ../actual
'
+cat >../status_uall.expect <<EOF &&
+A done/one
+A one
+A two
+?? dthree/three
+?? dtwo/two
+?? three
+EOF
+
+# Bypassing the untracked cache here is not desirable from an
+# end-user perspective, but is expected in the current design.
+# The untracked cache data stored for a -unormal run cannot be
+# correctly used in a -uall run - it would yield incorrect output.
+test_expect_success 'untracked cache is bypassed with -uall' '
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status -uall --porcelain >../actual &&
+ iuc status -uall --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+test_expect_success 'untracked cache remains after bypass' '
+ test-tool dump-untracked-cache >../actual &&
+ test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'if -uall is configured, untracked cache gets populated by default' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+ ....node-creation:3
+ ....gitignore-invalidation:1
+ ....directory-invalidation:0
+ ....opendir:4
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+cat >../dump_uall.expect <<EOF &&
+info/exclude $EMPTY_BLOB
+core.excludesfile $ZERO_OID
+exclude_per_dir .gitignore
+flags 00000000
+/ $ZERO_OID recurse valid
+three
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse valid
+three
+/dtwo/ $ZERO_OID recurse valid
+two
+EOF
+
+test_expect_success 'if -uall was configured, untracked cache is populated' '
+ test-tool dump-untracked-cache >../actual &&
+ test_cmp ../dump_uall.expect ../actual
+'
+
+test_expect_success 'if -uall is configured, untracked cache is used by default' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:0
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+# Bypassing the untracked cache here is not desirable from an
+# end-user perspective, but is expected in the current design.
+# The untracked cache data stored for a -all run cannot be
+# correctly used in a -unormal run - it would yield incorrect
+# output.
+test_expect_success 'if -uall is configured, untracked cache is bypassed with -unormal' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status -unormal --porcelain >../actual &&
+ iuc status -unormal --porcelain >../status.iuc &&
+ test_cmp ../status.expect ../status.iuc &&
+ test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+test_expect_success 'repopulate untracked cache for -unormal' '
+ git status --porcelain
+'
+
test_expect_success 'modify in root directory, one dir invalidation' '
: >four &&
test-tool chmtime =-240 four &&
--- /dev/null
+#!/bin/sh
+
+test_description='git commit summary'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_seq 101 200 >file &&
+ git add file &&
+ git commit -m initial &&
+ git tag initial
+'
+
+test_expect_success 'commit summary ignores rewrites' '
+ git reset --hard initial &&
+ test_seq 200 300 >file &&
+
+ git diff --stat >diffstat &&
+ git diff --stat --break-rewrites >diffstatrewrite &&
+
+ # make sure this scenario is a detectable rewrite
+ ! test_cmp_bin diffstat diffstatrewrite &&
+
+ git add file &&
+ git commit -m second >actual &&
+
+ grep "1 file" <actual >actual.total &&
+ grep "1 file" <diffstat >diffstat.total &&
+ test_cmp diffstat.total actual.total
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git mergetool
+
+Testing basic merge tools options'
+
+. ./test-lib.sh
+
+test_expect_success 'mergetool --tool=vimdiff creates the expected layout' '
+ . $GIT_BUILD_DIR/mergetools/vimdiff &&
+ run_unit_tests
+'
+
+test_done
. ./lib-gettext.sh
+doalarm () {
+ perl -e 'alarm shift; exec @ARGV' -- "$@"
+}
+
test_expect_success GETTEXT_LOCALE 'setup' '
test_write_lines "TILRAUN: Halló Heimur!" >file &&
git add file &&
test_cmp expected actual
'
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep avoid endless loop bug' '
+ echo " Halló" >leading-whitespace &&
+ git add leading-whitespace &&
+ doalarm 1 git grep --perl-regexp "^\s" leading-whitespace
+'
+
test_done
)
'
+#
+# Setup as before, and then explicitly sync imported branch, using a
+# different ref format.
+#
+test_expect_success 'git p4 sync existing branch without changes' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ git p4 sync --branch=refs/remotes/p4/depot >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, relative branch name.
+#
+test_expect_success 'git p4 sync existing branch with relative name' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=branch1 //depot@all &&
+ git p4 sync --branch=p4/branch1 >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, with a nested branch path, referenced different ways.
+#
+test_expect_success 'git p4 sync existing branch with nested path' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=p4/some/path //depot@all &&
+ git p4 sync --branch=some/path >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, with a full ref path outside the p4/* namespace.
+#
+test_expect_success 'git p4 sync branch explicit ref without p4 in path' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=refs/remotes/someremote/depot //depot@all &&
+ git p4 sync --branch=refs/remotes/someremote/depot >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+test_expect_success 'git p4 sync nonexistent ref' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ test_must_fail git p4 sync --branch=depot2 2>errs &&
+ test_i18ngrep "Perhaps you never did" errs
+ )
+'
+
+test_expect_success 'git p4 sync existing non-p4-imported ref' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ test_must_fail git p4 sync --branch=refs/heads/master 2>errs &&
+ test_i18ngrep "Perhaps you never did" errs
+ )
+'
+
test_expect_success 'clone two dirs' '
(
cd "$cli" &&
)
'
+test_expect_success 'sync specific detected branch' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" --detect-branches //depot@all &&
+ (
+ cd "$git" &&
+ git p4 sync --branch=depot/branch2 >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
test_expect_success 'import depot, branch detection, branchList branch definition' '
test_when_finished cleanup_git &&
test_create_repo "$git" &&
)
'
+test_expect_success SYMLINKS 'utf-8 with and without BOM in text file' '
+ (
+ cd "$cli" &&
+
+ # some utf8 content
+ echo some tǣxt >utf8-nobom-test &&
+
+ # same utf8 content as before but with bom
+ echo some tǣxt | sed '\''s/^/\xef\xbb\xbf/'\'' >utf8-bom-test &&
+
+ # bom only
+ dd bs=1 count=3 if=utf8-bom-test of=utf8-bom-empty-test &&
+
+ p4 add utf8-nobom-test utf8-bom-test utf8-bom-empty-test &&
+ p4 submit -d "add utf8 test files"
+ ) &&
+ test_when_finished cleanup_git &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git checkout refs/remotes/p4/master &&
+
+ echo some tǣxt >utf8-nobom-check &&
+ test_cmp utf8-nobom-check utf8-nobom-test &&
+
+ echo some tǣxt | sed '\''s/^/\xef\xbb\xbf/'\'' >utf8-bom-check &&
+ test_cmp utf8-bom-check utf8-bom-test &&
+
+ dd bs=1 count=3 if=utf8-bom-check of=utf8-bom-empty-check &&
+ test_cmp utf8-bom-empty-check utf8-bom-empty-test
+ )
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 metadata encoding
+
+This test checks that the import process handles inconsistent text
+encoding in p4 metadata (author names, commit messages, etc) without
+failing, and produces maximally sane output in git.'
+
+. ./lib-git-p4.sh
+
+python_target_version='2'
+
+###############################
+## SECTION REPEATED IN t9836 ##
+###############################
+
+# Please note: this test calls "git-p4.py" rather than "git-p4", because the
+# latter references a specific path so we can't easily force it to run under
+# the python version we need to.
+
+python_major_version=$(python -V 2>&1 | cut -c 8)
+python_target_binary=$(which python$python_target_version)
+if ! test "$python_major_version" = "$python_target_version" && test "$python_target_binary"
+then
+ mkdir temp_python
+ PATH="$(pwd)/temp_python:$PATH" && export PATH
+ ln -s $python_target_binary temp_python/python
+fi
+
+python_major_version=$(python -V 2>&1 | cut -c 8)
+if ! test "$python_major_version" = "$python_target_version"
+then
+ skip_all="skipping python$python_target_version-specific git p4 tests; python$python_target_version not available"
+ test_done
+fi
+
+remove_user_cache () {
+ rm "$HOME/.gitp4-usercache.txt" || true
+}
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+
+ p4_add_user "utf8_author" "ǣuthor" &&
+ P4USER=utf8_author &&
+ touch file1 &&
+ p4 add file1 &&
+ p4 submit -d "first CL has some utf-8 tǣxt" &&
+
+ p4_add_user "latin1_author" "$(echo æuthor |
+ iconv -f utf8 -t latin1)" &&
+ P4USER=latin1_author &&
+ touch file2 &&
+ p4 add file2 &&
+ p4 submit -d "$(echo second CL has some latin-1 tæxt |
+ iconv -f utf8 -t latin1)" &&
+
+ p4_add_user "cp1252_author" "$(echo æuthœr |
+ iconv -f utf8 -t cp1252)" &&
+ P4USER=cp1252_author &&
+ touch file3 &&
+ p4 add file3 &&
+ p4 submit -d "$(echo third CL has sœme cp-1252 tæxt |
+ iconv -f utf8 -t cp1252)" &&
+
+ p4_add_user "cp850_author" "$(echo Åuthor |
+ iconv -f utf8 -t cp850)" &&
+ P4USER=cp850_author &&
+ touch file4 &&
+ p4 add file4 &&
+ p4 submit -d "$(echo fourth CL hÅs some cp850 text |
+ iconv -f utf8 -t cp850)"
+ )
+'
+
+test_expect_success 'clone non-utf8 repo with strict encoding' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ test_must_fail git -c git-p4.metadataDecodingStrategy=strict p4.py clone --dest="$git" //depot@all 2>err &&
+ grep "Decoding perforce metadata failed!" err
+'
+
+test_expect_success 'check utf-8 contents with passthrough strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=passthrough p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some utf-8 tǣxt" actual &&
+ grep "ǣuthor" actual
+ )
+'
+
+test_expect_success 'check latin-1 contents corrupted in git with passthrough strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=passthrough p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ badly_encoded_in_git=$(echo "some latin-1 tæxt" | iconv -f utf8 -t latin1) &&
+ grep "$badly_encoded_in_git" actual &&
+ bad_author_in_git="$(echo æuthor | iconv -f utf8 -t latin1)" &&
+ grep "$bad_author_in_git" actual
+ )
+'
+
+test_expect_success 'check utf-8 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some utf-8 tǣxt" actual &&
+ grep "ǣuthor" actual
+ )
+'
+
+test_expect_success 'check latin-1 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some latin-1 tæxt" actual &&
+ grep "æuthor" actual
+ )
+'
+
+test_expect_success 'check cp-1252 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "sœme cp-1252 tæxt" actual &&
+ grep "æuthœr" actual
+ )
+'
+
+test_expect_success 'check cp850 contents parsed with correct fallback' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback -c git-p4.metadataFallbackEncoding=cp850 p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "hÅs some cp850 text" actual &&
+ grep "Åuthor" actual
+ )
+'
+
+test_expect_success 'check cp850-only contents escaped when cp1252 is fallback' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "h%8Fs some cp850 text" actual &&
+ grep "%8Futhor" actual
+ )
+'
+
+test_expect_success 'check cp-1252 contents on later sync after clone with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$cli" &&
+ P4USER=cp1252_author &&
+ touch file10 &&
+ p4 add file10 &&
+ p4 submit -d "$(echo later CL has sœme more cp-1252 tæxt |
+ iconv -f utf8 -t cp1252)"
+ ) &&
+ (
+ cd "$git" &&
+
+ git p4.py sync --branch=master &&
+
+ git log p4/master >actual &&
+ grep "sœme more cp-1252 tæxt" actual &&
+ grep "æuthœr" actual
+ )
+'
+
+############################
+## / END REPEATED SECTION ##
+############################
+
+test_expect_success 'passthrough (latin-1 contents corrupted in git) is the default with python2' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=passthrough p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ badly_encoded_in_git=$(echo "some latin-1 tæxt" | iconv -f utf8 -t latin1) &&
+ grep "$badly_encoded_in_git" actual
+ )
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 metadata encoding
+
+This test checks that the import process handles inconsistent text
+encoding in p4 metadata (author names, commit messages, etc) without
+failing, and produces maximally sane output in git.'
+
+. ./lib-git-p4.sh
+
+python_target_version='3'
+
+###############################
+## SECTION REPEATED IN t9835 ##
+###############################
+
+# Please note: this test calls "git-p4.py" rather than "git-p4", because the
+# latter references a specific path so we can't easily force it to run under
+# the python version we need to.
+
+python_major_version=$(python -V 2>&1 | cut -c 8)
+python_target_binary=$(which python$python_target_version)
+if ! test "$python_major_version" = "$python_target_version" && test "$python_target_binary"
+then
+ mkdir temp_python
+ PATH="$(pwd)/temp_python:$PATH" && export PATH
+ ln -s $python_target_binary temp_python/python
+fi
+
+python_major_version=$(python -V 2>&1 | cut -c 8)
+if ! test "$python_major_version" = "$python_target_version"
+then
+ skip_all="skipping python$python_target_version-specific git p4 tests; python$python_target_version not available"
+ test_done
+fi
+
+remove_user_cache () {
+ rm "$HOME/.gitp4-usercache.txt" || true
+}
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+
+ p4_add_user "utf8_author" "ǣuthor" &&
+ P4USER=utf8_author &&
+ touch file1 &&
+ p4 add file1 &&
+ p4 submit -d "first CL has some utf-8 tǣxt" &&
+
+ p4_add_user "latin1_author" "$(echo æuthor |
+ iconv -f utf8 -t latin1)" &&
+ P4USER=latin1_author &&
+ touch file2 &&
+ p4 add file2 &&
+ p4 submit -d "$(echo second CL has some latin-1 tæxt |
+ iconv -f utf8 -t latin1)" &&
+
+ p4_add_user "cp1252_author" "$(echo æuthœr |
+ iconv -f utf8 -t cp1252)" &&
+ P4USER=cp1252_author &&
+ touch file3 &&
+ p4 add file3 &&
+ p4 submit -d "$(echo third CL has sœme cp-1252 tæxt |
+ iconv -f utf8 -t cp1252)" &&
+
+ p4_add_user "cp850_author" "$(echo Åuthor |
+ iconv -f utf8 -t cp850)" &&
+ P4USER=cp850_author &&
+ touch file4 &&
+ p4 add file4 &&
+ p4 submit -d "$(echo fourth CL hÅs some cp850 text |
+ iconv -f utf8 -t cp850)"
+ )
+'
+
+test_expect_success 'clone non-utf8 repo with strict encoding' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ test_must_fail git -c git-p4.metadataDecodingStrategy=strict p4.py clone --dest="$git" //depot@all 2>err &&
+ grep "Decoding perforce metadata failed!" err
+'
+
+test_expect_success 'check utf-8 contents with passthrough strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=passthrough p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some utf-8 tǣxt" actual &&
+ grep "ǣuthor" actual
+ )
+'
+
+test_expect_success 'check latin-1 contents corrupted in git with passthrough strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=passthrough p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ badly_encoded_in_git=$(echo "some latin-1 tæxt" | iconv -f utf8 -t latin1) &&
+ grep "$badly_encoded_in_git" actual &&
+ bad_author_in_git="$(echo æuthor | iconv -f utf8 -t latin1)" &&
+ grep "$bad_author_in_git" actual
+ )
+'
+
+test_expect_success 'check utf-8 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some utf-8 tǣxt" actual &&
+ grep "ǣuthor" actual
+ )
+'
+
+test_expect_success 'check latin-1 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "some latin-1 tæxt" actual &&
+ grep "æuthor" actual
+ )
+'
+
+test_expect_success 'check cp-1252 contents with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "sœme cp-1252 tæxt" actual &&
+ grep "æuthœr" actual
+ )
+'
+
+test_expect_success 'check cp850 contents parsed with correct fallback' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback -c git-p4.metadataFallbackEncoding=cp850 p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "hÅs some cp850 text" actual &&
+ grep "Åuthor" actual
+ )
+'
+
+test_expect_success 'check cp850-only contents escaped when cp1252 is fallback' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "h%8Fs some cp850 text" actual &&
+ grep "%8Futhor" actual
+ )
+'
+
+test_expect_success 'check cp-1252 contents on later sync after clone with fallback strategy' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git -c git-p4.metadataDecodingStrategy=fallback p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$cli" &&
+ P4USER=cp1252_author &&
+ touch file10 &&
+ p4 add file10 &&
+ p4 submit -d "$(echo later CL has sœme more cp-1252 tæxt |
+ iconv -f utf8 -t cp1252)"
+ ) &&
+ (
+ cd "$git" &&
+
+ git p4.py sync --branch=master &&
+
+ git log p4/master >actual &&
+ grep "sœme more cp-1252 tæxt" actual &&
+ grep "æuthœr" actual
+ )
+'
+
+############################
+## / END REPEATED SECTION ##
+############################
+
+
+test_expect_success 'fallback (both utf-8 and cp-1252 contents handled) is the default with python3' '
+ test_when_finished cleanup_git &&
+ test_when_finished remove_user_cache &&
+ git p4.py clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log >actual &&
+ grep "sœme cp-1252 tæxt" actual &&
+ grep "æuthœr" actual
+ )
+'
+
+test_done
;;
esac
-# Add libc MALLOC and MALLOC_PERTURB test
-# only if we are not executing the test with valgrind
+# Add libc MALLOC and MALLOC_PERTURB test only if we are not executing
+# the test with valgrind and have not compiled with SANITIZE=address.
if test -n "$valgrind" ||
+ test -n "$SANITIZE_ADDRESS" ||
test -n "$TEST_NO_MALLOC_CHECK"
then
setup_malloc_check () {
test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2
test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK
+test -n "$GIT_VALGRIND_ENABLED" && test_set_prereq VALGRIND
if test -z "$GIT_TEST_CHECK_CACHE_TREE"
then
static VOLATILE_LIST_HEAD(tempfile_list);
+static void remove_template_directory(struct tempfile *tempfile,
+ int in_signal_handler)
+{
+ if (tempfile->directorylen > 0 &&
+ tempfile->directorylen < tempfile->filename.len &&
+ tempfile->filename.buf[tempfile->directorylen] == '/') {
+ strbuf_setlen(&tempfile->filename, tempfile->directorylen);
+ if (in_signal_handler)
+ rmdir(tempfile->filename.buf);
+ else
+ rmdir_or_warn(tempfile->filename.buf);
+ }
+}
+
static void remove_tempfiles(int in_signal_handler)
{
pid_t me = getpid();
unlink(p->filename.buf);
else
unlink_or_warn(p->filename.buf);
+ remove_template_directory(p, in_signal_handler);
p->active = 0;
}
tempfile->owner = 0;
INIT_LIST_HEAD(&tempfile->list);
strbuf_init(&tempfile->filename, 0);
+ tempfile->directorylen = 0;
return tempfile;
}
return tempfile;
}
+struct tempfile *mks_tempfile_dt(const char *directory_template,
+ const char *filename)
+{
+ struct tempfile *tempfile;
+ const char *tmpdir;
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+ size_t directorylen;
+
+ if (!ends_with(directory_template, "XXXXXX")) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ strbuf_addf(&sb, "%s/%s", tmpdir, directory_template);
+ directorylen = sb.len;
+ if (!mkdtemp(sb.buf)) {
+ int orig_errno = errno;
+ strbuf_release(&sb);
+ errno = orig_errno;
+ return NULL;
+ }
+
+ strbuf_addf(&sb, "/%s", filename);
+ fd = open(sb.buf, O_CREAT | O_EXCL | O_RDWR, 0600);
+ if (fd < 0) {
+ int orig_errno = errno;
+ strbuf_setlen(&sb, directorylen);
+ rmdir(sb.buf);
+ strbuf_release(&sb);
+ errno = orig_errno;
+ return NULL;
+ }
+
+ tempfile = new_tempfile();
+ strbuf_swap(&tempfile->filename, &sb);
+ tempfile->directorylen = directorylen;
+ tempfile->fd = fd;
+ activate_tempfile(tempfile);
+ return tempfile;
+}
+
struct tempfile *xmks_tempfile_m(const char *filename_template, int mode)
{
struct tempfile *tempfile;
close_tempfile_gently(tempfile);
unlink_or_warn(tempfile->filename.buf);
+ remove_template_directory(tempfile, 0);
deactivate_tempfile(tempfile);
*tempfile_p = NULL;
}
FILE *volatile fp;
volatile pid_t owner;
struct strbuf filename;
+ size_t directorylen;
};
/*
return xmks_tempfile_m(filename_template, 0600);
}
+/*
+ * Attempt to create a temporary directory in $TMPDIR and to create and
+ * open a file in that new directory. Derive the directory name from the
+ * template in the manner of mkdtemp(). Arrange for directory and file
+ * to be deleted if the program exits before they are deleted
+ * explicitly. On success return a tempfile whose "filename" member
+ * contains the full path of the file and its "fd" member is open for
+ * writing the file. On error return NULL and set errno appropriately.
+ */
+struct tempfile *mks_tempfile_dt(const char *directory_template,
+ const char *filename);
+
/*
* Associate a stdio stream with the temporary file (which must still
* be open). Return `NULL` (*without* deleting the file) on error. The
/* Create temporary file in the same directory as the original */
tail = strrchr(file, '/');
- if (tail != NULL)
+ if (tail)
strbuf_add(&filename_template, file, tail - file + 1);
strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX");
args.self_contained_and_connected;
data->options.connectivity_checked = args.connectivity_checked;
- if (refs == NULL)
+ if (!refs)
ret = -1;
if (report_unmatched_refs(to_fetch, nr_heads))
ret = -1;
#include "refs.h"
#include "attr.h"
#include "split-index.h"
+#include "sparse-index.h"
#include "submodule.h"
#include "submodule-config.h"
#include "fsmonitor.h"
o->result.fsmonitor_last_update =
xstrdup_or_null(o->src_index->fsmonitor_last_update);
+ if (!o->src_index->initialized &&
+ !repo->settings.command_requires_full_index &&
+ is_sparse_index_allowed(&o->result, 0))
+ o->result.sparse_index = 1;
+
/*
* Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
*/
/* Trailing "**" matches everything. Trailing "*" matches
* only if there are no more slash characters. */
if (!match_slash) {
- if (strchr((char*)text, '/') != NULL)
+ if (strchr((char *)text, '/'))
return WM_NOMATCH;
}
return WM_MATCH;
return 0;
d = readdir_skip_dot_and_dotdot(dir);
- if (d != NULL)
+ if (d)
ret = 1;
closedir(dir);
return ret;
FILE *xfdopen(int fd, const char *mode)
{
FILE *stream = fdopen(fd, mode);
- if (stream == NULL)
+ if (!stream)
die_errno("Out of memory? fdopen failed");
return stream;
}
if (stat(filename, &st))
return error_errno("Could not stat %s", filename);
- if ((f = fopen(filename, "rb")) == NULL)
+ if (!(f = fopen(filename, "rb")))
return error_errno("Could not open %s", filename);
sz = xsize_t(st.st_size);
ptr->ptr = xmalloc(sz ? sz : 1);
*xscr = xch;
}
- if (*xscr == NULL)
+ if (!*xscr)
return NULL;
lxch = *xscr;
#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
-#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
#define XDL_LE32_PUT(p, v) \
do { \
unsigned char *__p = (unsigned char *) (p); \
memset(rhash, 0, hsize * sizeof(xrecord_t *));
nrec = 0;
- if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
+ if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
for (top = blk + bsize; cur < top; ) {
prev = cur;
hav = xdl_hash_record(&cur, top, xpp->flags);
long nl = 0, size, tsize = 0;
char const *data, *cur, *top;
- if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
+ if ((cur = data = xdl_mmfile_first(mf, &size))) {
for (top = data + size; nl < sample && cur < top; ) {
nl++;
if (!(cur = memchr(cur, '\n', top - cur)))